CONCEPTO DE VARIABLE
¿Qué es una variable?
- Clasificación de variables según su duración.
Desde el punto de vista matemático, se tiene lo siguiente:
-
Una variable es una función que toma valores pertenecientes a un determinado conjunto, su dominio, y que produce como resultado su propio valor.
-
El conjunto de valores que pueden tomar la variables es infinito, salvo indicación expresa en contra. A fin de cuentas, se trata de símbolos y un símbolo puede representar cualquier cosa. El problema surgirá, claro, al operar con el símbolo.
-
Se supone que las variables denotan valores que posiblemente tenga un número infinito de cifras significativas.
Desde el punto de vista informático, una variable es un determinado segmento de memoria de proceso, del cual se conoce:
-
La posición de memoria en que comienza (dirección).
-
El número de bytes de que consta (extensión, un número finito).
-
La codificación binaria correcta para interpretar su contenido (tipo).
Al ser finito el número de bytes que se emplea para codificar una variable, surgen diferencias importantes respecto al concepto matemático habitual:
-
El conjunto de valores que se puede representar mediante una variable es finito, no infinito.
-
El número de cifras significativas que admite una variable es finito, no infinito
Lo anterior implica, insistimos, que una variable numérica de un ordenador puede tomar tan solo un número finito de valores, y no cualquier valor numérico. Además, cada uno de esos valores tendrá un número finito de cifras significativas; este número de cifras significativas va a ser el mismo para todas las variables de un mismo tipo.
Las diferencias no acaban aquí. Cuando se utiliza una variable en un programa, la información que se almacena en esa variable va a ser utilizada por las distintas partes de ese programa. Consideremos, por ejemplo, el siguiente programa escrito en C:
#include
float volumen;
int main(int argc, char * argv[])
{
float x, y, z;
printf("\n\nVolumen de un prisma\n\n");
printf("Escriba la longitud, anchura y altura: ");
scanf("%f %f %f", &x, &y, &z);
volumen = x * y * z;
printf("\n\nEl volumen del prisma es %f\n", volumen);
printf("\nTerminación normal del programa.\n");
return 0;
}
/*
[maxus:~/Documents/tests] coti% ./a.out
Volumen de un prisma
Escriba la longitud, anchura y altura: 2 3 4
El volumen del prisma es 24.000000
Terminación normal del programa.
[maxus:~/Documents/tests] coti%
*/
La tabla de variables utilizadas en función
main()
es la siguiente siguiente:
Nombre | Dirección | Extensión | Tipo | Valor |
volumen | &volumen | 4 | float |
x | &x | 4 | float |
y | &t | 4 | float |
z | &z | 4 | float |
(Obsérvese que la columna de valores está vacía al no haberse considerado ejecución concreta del programa; de hecho, cuando el programa queda a la espera de que el usuario inserte valores (en la sentencia
scanf()
), el contenido de las variables
x
,
y
y
z
puede ser cualquier cosa.)
Otra diferencia importante de las variables informáticas con respecto a las variables matemáticas es lo que podríamos llamar su
duración
. Una variable matemática, (se utilice o no) dura tanto como nuestro interés por ella. En un programa de ordenador, las variables tienen, sin embargo, cuatro posibles duraciones:
-
Automática
. Este es el caso de las variables que se declaran dentro de una función, conocidas también como variables locales (En nuestro ejemplo,
x
,
y
y
z
son variables locales). Las variables locales automáticas mantienen su valor mientras se esté ejecutando el código de la función en que hayan sido declaradas (
Mas exactamente, se reserva espacio para ellas en la pila (mediante un
offset
negativo respecto al puntero de pila
). Cuando concluye la función, el espacio reservado en la pila se vuelve a poner a disposición del programa, y se pierde el contenido de las variables locales automáticas. Las variables locales (automáticas o estáticas) sólo se pueden utilizar en el código contenido en la función en que están declaradas.
-
Estática
. Si se antepone la palabra reservada
static
a la declaración de una variable local, ésa variable pasa a crearse en la zona destinada a datos estáticos; por tanto, el espacio reservado para ella se mantendrá operativo durante toda la ejecución del programa. Por tanto, las variables estáticas se caracterizan porque su valor se mantiene entre ejecuciones de la función, esto es, su valor no se pierde cuando finaliza la ejecución de la función en que hayan sido declaradas. Las variables locales (automáticas o estáticas) sólo se pueden utilizar en el código contenido en la función en que están declaradas (si, esto se ha repetido intencionadamente).
-
Global
. Si se declara una variable fuera de toda función, la variable es automáticamente estática en el sentido del párrafo anterior, y el espacio reservado para ella se mantiene operativo durante todao el programa, si que se pierda su valor. Además, las variables globales se pueden utilizar en el código de cualquier función del programa, por tanto, su valor se puede leer y modificar desde cualquier función del programa.
-
Dinámica
. El lenguaje C, entre otros, aporta la posibilidad de reservar espacio para almacenar variables mientras se está ejecutando el programa. Las variables que se crean de esta manera, denominadas dinámicas, siguen existiendo mientras el programa no decida lo contrario, esto es, las variables reservadas dinámicamente se pueden crear y destruir al arbitrio del programador. La memoria utilizada para una variable dinámica, cuando ésta es destruida, puede reutilizarse (reservarse de nuevo) para crear otras variables. El acceso a las variables dinámicas se realiza a través de otras variables de un tipo especial denominado
puntero
(
pointer
).
El uso de variables globales no es aconsejable por dos razones fundamentales:
-
Supone un grave riesgo de cometer errores. Los programas suelen estar formados por centenares de funciones creadas por múltiples programadores. Es fácil modificar una variable global de manera inadvertida, produciéndose así un error muy difícil de detectar y corregir (el programador A modifica el valor de la variable global
contador
, que utilizaba el programador B para algo completamente distinto).
-
Dificultan o imposibilitan la reutilización de código. Si una función hace uso de una variable global y se intenta utilizar esa función en otro programa, el compilador señalará un error a menos que se defina globalmente la variable en cuestión. Entonces incurrimos en los riesgos del apartado anterior, y el uso de esa función hace que los riesgos se propaguen.
Por otra parte, es posible intercambiar información entre funciones sin tener que recurrir a variables globales, mediante el uso de parámetros
(q.v.)
. Como consecuencia, es buena práctica de programación prescindir del uso de variables globales. Además, el paradigma de Programación orientada a objetos (POO) aporta el concepto de clase, facilitando aún más la comunicación entre distintas partes de un programa sin recurrir a variables globales.
Necesidad de una comprobación estricta de tipos
Según lo expuesto, todas las variables que se utilizan en un programa (con excepción de las dinámicas, que no tienen nombre) tienen un nombre, una extensión y un tipo. Esto hace fácil realizar operaciones con variables, y también detectar posibles errores de programación, como sumar un número y una cadena.
¿Qué ocurriría en un lenguaje sin comprobación de tipos?
Supongamos que se suman dos variables numéricas, dos enteros, por ejemplo, y consideremos las nefastas consecuencias de un error en el tipo de una variable.
int x, z;
char t[4] = "aaa";
z = x + t;
Un bloque de memoria de 4 bytes puede contener un número entero (
int
), pero también un número real (
float
), cuatro caracteres (
char
), o cualquier puntero, y otras muchas combinaciones de variables. Si se aplica el algoritmo correcto para sumar un
int
, y una de las variables usadas estaba codificada empleando las convenciones adecuadas para almacenar cuatro
char
, el resultado será un
int
de valor impredecible, y carente de sentido a efectos del valor esperado. Desde el punto de vista del cálculo, el ordenador no detectará error alguno, porque cualquier trama de bits formada por cuatro bytes representa un número entero.
Para evitar estos errores y otros de tipo similar, el compilador se asegura de que las expresiones de que consta el programa no contengan errores de este tipo, esto es, impide construir expresiones en que los tipos de las variables empleadas no sean compatibles. (
Esto va más allá de asegurar meramente que se suman
int
con
int
, por ejemplo. De hecho, se pueden sumar
int
con
float
... pero el resultado será un
float
, no un
int
. Este mecanismo, denominado de promoción de tipos, realiza automáticamente las conversiones de tipo necesarias para garantizar que no haya pérdida de información. De este modo se consigue un lenguaje más flexible.
) El compilador no permite, por ejemplo, intentar escribir un
float
en un
char
. De este modo, se detectan posibles errores que, de no ser corregidos, producirían programas incorrectos y exigirían al programador buscar personalmente estos errores. (
El compilador, permite, sin embargo, reinterpretar (refundir) el tipo de una variable, interpretándola como si fuese de otro tipo. Se impone la restricción de que ambos tipos sean de igual extensión. Es responsabilidad del programador asegurarse de que el resultado de esta nueva interpretación tenga sentido
).
Necesidad de la declaración de variables
No debemos olvidar que un compilador no deja de ser un traductor de programas a lenguaje máquina. Desde el punto de vista de la computadora, su "obligación" es leer y ejecutar instrucciones, comenzando en cierto punto de la memoria de proceso (el punto de entrada de nuestro programa). Esas instrucciones leerán y escribirán en memoria, pero la memoria no es otra cosa que una lista numerada de posiciones. El concepto de variable, tal como lo interpreta un programador en un lenguaje de alto nivel, se traduce a una dirección de comienzo en el mundo de bajo nivel. Esa dirección de comienzo será utilizada por una instrucción de procesamiento para realizar una operación, y eso es todo. El procesador realiza automáticamente (como un autómata) la operación marcada por la instrucción y no conoce otra cosa. Es responsabilidad del programador ir reservando zonas de memoria en las cuales se almacenarán informaciones importantes (variables). El compilador realiza esta tarea sin que sepamos nada sobre direcciones, y se encarga de generar las instrucciones apropiadas para el tipo de datos empleado al codificar el contenido de las variables. Si no se declaran las variables, el compilador tiene que hacer suposiciones sobre su tipo:
-
Algunos lenguajes optan por pensar que todo son cadenas alfanuméricas, y efectúan una traducción a valores numéricos cuando es preciso realizar operaciones numéricas.
-
Otros determinan que debe asignarse a la variable a partir de la letra por la que comienza; tal sería el caso de
FORTRAN
(aunque cuenta con la directriz
IMPLICIT NONE
, que exige la declaración explícita de todas las variables).
-
Y otros emplean un carácter situado al final del identificador de la variable para determinar el tipo.
Finalmente, algunos compiladores exigen que toda variable utilizada en el programa sea "declarada" previamente, esto es, se exige que la variable aparezca en una expresión que indica su tipo. Tal es el caso, entre otros, de C/C++ y Java. La pequeña incomodidad que suponer declarar las variables se ve compensada ampliamente por la imposibilidad de que aparezcan errores debidos a variables usadas sin ser declaradas, y cuyo valor pueda haber escapado a nuestro control.
¿Cómo se comunica una computadora con el Usuario? Concepto de E/S
Toda la codificación, almacenamiento y tratamiento de información se realiza en el interior del ordenador, sin que el usuario pueda apreciar nada. Para poder ver algo es preciso que la información pase a un periférico como la pantalla del ordenador (o más exactamente, al adaptador de vídeo) o a una impresora, a un altavoz (más exactamente, al adaptador de sonido), etc. En todo caso, se aprecia la necesidad de hacer que el ordenador reciba y proporciona información: en su conjunto, las operaciones que traspasan información desde el ordenador al mundo exterior o viceversa se conocen con el nombre de operaciones de entrada-salida, o
E/S
para abreviar. En el caso del lenguaje C, la lectura de información a través del teclado se realiza mediante la función
scanf()
, entre otras, y la impresión de información en pantalla se realiza a través de la función
printf()
, entre otras. Véase un ejemplo.
#include<stdio.h>
int main(int argc, char * argv[])
{
char nombre[100];
int edad;
printf("Dígame como se llama, por favor: ");
scanf("%s", nombre);
printf("Dígame su edad en años, por favor: ");
scanf("%d", &edad);
printf("Se llama usted %s y ha vivido %d días.\n",
nombre,
365*edad);
return 0;
}
Las sentencias que comienzan por
printf()
hacen pasar información de la memoria del ordenador a la pantalla; por su parte,la sentencias que empiezan por
scanf()
hacen pasar información del teclado a la memoria del ordenador. Existen otras sentencias que permiten pasar información de memoria a disco y de disco a memoria, y también es posible enviar y recibir información a través de distintos tipos de redes. Todas estas operaciones se agrupan con el nombre de operaciones de Entrada-Salida, y constituyen una parte fundamental de las tareas de programación.