martes, 18 de febrero de 2014

Las Variables en el Lenguaje C

Introducción


Si se deseara imprimir los resultados de multiplicar un número fijo por otro que adopta valores entre 0 y 9 , la forma normal de programar esto sería crear una constante para el primer número y un par de variables para el segundo y para el resultado del producto. Una variable, en realidad , no es más que un nombre para identificar una (o varias) posiciones de memoria donde el programa guarda los distintos valores de una misma entidad . Un programa debe definir todas las variables que utilizará antes de comenzar a usarlas, a fin de indicarle al compilador de que tipo serán y, por tanto, cuánta memoria debe destinar para albergar a cada una de ellas. Veamos un ejemplo:

#include <stdio.h>

main()
{
 int multiplicador; /* defino multiplicador como un entero */
 int resultado;  /* defino resultado como un entero */

 multiplicador = 1000 ; /* le asigno valores */
 resultado = 2 * multiplicador ;
 printf("Resultado = %d\n", resultado); /* muestro el resultado */

 return 0;
}



Así, una variable es un lugar donde se puede almacenar temporalmente un dato. En C las variables tienen un nombre que las identifica, y sirve para hacer referencia a ellas. También tienen un tipo, que es el tipo de datos que puede almacenar. El valor de las variables es, como su propio nombre indica, variable. Podemos alterar su valor en cualquier punto del programa.

Identificadores y tipos

Para dar un nombre a una variable tenemos que usar un identificador. La longitud de un identificador puede variar entre uno y varios caracteres, por lo general, 32. En la mayoría de los casos el primer carácter debe ser una letra o un símbolo de subrayado. A continuación se muestran varios ejemplos de nombres de identificadores correctos e incorrectos:

CorrectoIncorrecto
cuenta1cuenta
prueba23hola!
puerto_paralelopuerto..paralelo

El lenguaje C es sensible a mayúsculas y minúsculas (case sensitive), de modo que no es lo mismo para el compilador el identificador cuenta que otro denominado Cuenta.

Los compiladores reservan determinados términos ó palabras claves (keywords), para el uso sintáctico del lenguaje, tales como: asm, auto, break, case, char, do, for, etc. Si bien estas palabras están definidas para el ANSI C, los distintos compiladores extienden esta definición a otros términos, por lo que es aconsejable leer la tabla completa de palabras reservadas del compilador que se vaya a usar, para no utilizarlas en nombres de variables.

Para crear una variable en un lugar determinado del un programa escribiremos primero el tipo de variable y luego el identificador con el que queremos nombrar la variable, seguido todo de un ';'. A esto se le denomina definir una variable. La forma general de la definición es:

tipo lista_identificadores;

Por ejemplo:

 int numero; /* crea la variable numero, de tipo número entero */
 char letra; /* crea la variable letra, de tipo carácter*/
 float a, b; /* crea dos variables a y b, de tipo número de coma flotante */

Las variables se pueden inicializar, es decir, establecer un valor inicial, en el momento de creación. Para ello, detrás del identificador ponemos el carácter '=' seguido del valor inicial. Los valores iniciales pueden ser cualquier constante válida para el tipo de variable que creemos. Por ejemplo:

int numero = 0; /* crea la variable entera numero
y la inicializa a 0*/
 char letra = 'p'; /* crea la variable carácter letra
     y la inicializa a 'p' */
 float a=57.1, b=1.2E-5; /* crea las variables a y b,
     de tipo número de coma flotante, y los inicializa
     a 57.1 y 0.000012, respectivamente*/

Existen tres sitios donde se pueden definir variables:
  • fuera de todas las funciones (variables globales).
  • dentro de las funciones (variables locales)
  • en la definición de parámetros de funciones (parámetros formales)
El tipo de datos carácter consta de un único carácter y se suele representar por su carácter en código ASCII situado entre apóstrofes. Por ejemplo:

 'p' /* la letra p minúscula */
 '1' /* el numero 1 */
 ' ' /* el carácter en blanco */

Hay otras formas de representar caracteres, que se emplean cuando es un carácter que no se puede introducir directamente desde el teclado. Para ello debemos conocer su código ASCII. Para representar el carácter de numero ascii 27, (el código para el carácter ESCAPE), basta colocar el número ascii en el sistema octal precedido de la barra atrás, y todo ello entre apóstrofes, tal y como hacemos para los demás caracteres:

 '\27' /* representa el código ESCAPE, de ASCII 27 */

En C hay algunos caracteres especiales que se usan frecuentemente. Estos caracteres tiene una representación especial. Algunos de ellos son:

Código
Significado
Valor ASCII
(decimal)
Valor ASCII
(hexadecimal)
'\n'nueva línea100x0A
'\r'retorno de carro130x0D
'\f'nueva página (form feed)2x0C
'\t'tabulador horizontal90x09
'\b'retroceso (backspace)80x08
'\''comilla simple390x27
'\"'comillas40x22
'\\ 'barra invertida920x5C
'\? 'interrogación630x3F
'\nnn'cualquier carácter (donde nnn es el código ASCII expresado en octal)
'\xnn'cualquier carácter (donde nn es el código ASCII expresado en hexadecimal)

Un tipo entero consiste en un número sin parte decimal, aunque puede tener signo. Generalmente con el tipo de datos entero no representamos números muy grandes. 

Son ejemplos:

     0
     124
     -2000

El tipo de datos entero nos permitir hacer operaciones aritméticas, como la suma y la multiplicación. El tipo de datos entero es quizás el más importante de todos los tipos de datos, y muchas veces es el tipo de datos por defecto, es decir, cuando el compilador se encuentre con un dato y no le hayamos indicado cuál es su tipo, supondrá que es un entero.

C también posee el tipo de datos en coma flotante, que es el que usan las calculadoras científicas en sus operaciones aritméticas.

Los tipos de datos atómicos definidos por C son:
  • caracteres
  • números enteros
  • números en coma flotante
Los caracteres son representados por char, los enteros por short, int, long, y los números en coma flotante por float ydouble. Los cinco tipos de datos atómicos son:

IdentificadorTipoTamaño en bitsIntervalo
charCarácter8 (normalmente)0  a  255
intEntero con signo16 (depende de la implementación)-32 768  a  32 767
floatFlotante simple32 (normalmente)3.4E-38  a  3.4E+38
(aprox. 6 dígitos de precisión)
doubleFlotante doble64 (normalmente)1.7E-308  a  1.7E+308
(aprox. 12 dígitos de precisión)
voidNada
Genérico
0sin valor

El tipo representado por void puede significar dos cosas distintas, según su utilización. Puede significar nada, o sea, si una función devuelve un valor de tipo void no devuelve ningún resultado; o puede significar cualquier cosa, como puede ser un puntero a void (un puntero genérico a cualquier tipo de dato). Más adelante veremos su utilización.

A excepción del tipo void, los otros tipos básicos admiten variantes. Esto se consigue utilizando modificadores de tipo que precedan la definición del tipo:
  • signed
  • unsigned
  • long
  • short
La palabra unsigned en realidad es un modificador aplicable a tipos enteros, aunque si no se especifica un tipo se suponeint. El modificador es una palabra clave de C que indica que una variable, o función, no se comporta de la forma normal. Hay también un modificador signed, pero como los tipos son por defecto con signo, casi no se utiliza. Las variables de coma flotante son siempre con signo y, en el caso en que el exponente sea positivo, puede obviarse el signo del mismo. La siguiente tabla muestra todas las combinaciones permitidas que se ajustan al estándar ANSI.

Tipo
bits
Intervalo
char
8
-128 a 127
unsigned char
8
0 a 255
signed char
8
-128 a 127
int
16
-32 768 a 32 767
unsigned int
16
0 a 65535
signed int
16
igual que int
short int
8
-128 a 127
unsigned short int
8
0 a 255
signed short int
8
igual que short int
long int
32
-2 147 483 648 a 2147 483 647
signed long int
32
-2 147 483 648 a 2 147 483 647
unsigned long int
32
0 a 4 294 967 296
float
32
3.4E-38 a 3.4E+38
double
64
1.7E-308 a 1.7E+308
long double
128
3.4E-4932 a 3.4E+4932
(aprox. 24 dígitos de precisión)


Es preciso tener presente que en sistemas que permiten longitudes de palabra mayores de 16 bits un short int puede no ser igual que un signed char.

Veamos el siguiente programa de ejemplo acerca del uso de variables:

#include <stdio.h>

main()
{
 float cels, fahr;

 cels = 25.0;   /* Temperatura en º C  */
 fahr = 32.0 + 9.0 * fahr / 5.0; /* Temperatura en º Fahrenheit */

 printf(">>> %f ºC son %f ºF\n", cels, fahr );

}

En él se definen dos variables float, se asigna un valor a la primera y se calcula la segunda mediante una expresión aritmética. Las asignaciones en C también son una expresión, por lo que se pueden utilizar como parte de otra expresión, pero según que prácticas de este tipo no son muy recomendables ya que reducen la legibilidad del programa. En la instrucción printf, el símbolo %f indica que se imprime un número en coma flotante.

Un tipo de datos importante en C es la cadena de caracteres. Está formada por un conjunto de caracteres encerrados entre comillas. Podemos usar todos los caracteres del conjunto ASCII, incluso los especiales. Los caracteres normales se incluyen entre las comillas tal cual, sin necesidad de apóstrofes, y los especiales se incluyen utilizando la representación del C. Por ejemplo:

 "Bienvenido a la programación en lenguaje C\n"

En este ejemplo observamos la cadena de caracteres "Bienvenido a la programación en lenguaje C", a la que hemos añadido un carácter de retorno de carro al final. El motivo de ello es que cuando la imprimamos el carácter de retorno de carro, \n, actuará como una orden que obligar al cursor a avanzar una línea y situarse al principio de la siguiente.



Tamaño de las variables

En muchos programas es necesario conocer el tamaño (cantidad de bytes) que ocupa una variable; por ejemplo, en el caso de querer reservar memoria para un conjunto de ellas. Lamentablemente, este tamaño es dependiente del compilador que se use, lo que producirá, si definimos rígidamente (con un número dado de bytes) el espacio requerido para almacenarlas, un problema serio si luego se quiere compilar el programa con un compilador distinto del original. Para salvar este problema y mantener la portabilidad, es conveniente que cada vez que haya que referirse al tamaño en bytes de las variables, se haga mediante un operador llamado sizeof que calcula sus requerimientos de almacenaje. Está también permitido el uso desizeof con un tipo de variable, es decir:


 sizeof(int);

 sizeof(char);

 sizeof(long double);
, etc.


Duración de las variables

Las variables pueden ser de dos tipos:
  • estáticas
  • dinámicas
Las estáticas se crean al principio del programa y duran mientras el programa se ejecute. Las variables son dinámicas si son creadas dentro de una función. Su existencia está ligada a la existencia de la función. Se crean cuando la función es llamada y se destruyen cuando la función o subrutina devuelve el control a la rutina que la llamó.

Las variables estáticas se utilizan para almacenar valores que se van a necesitar a lo largo de todo el programa. Las variables dinámicas se suelen utilizar para guardar resultados intermedios en los cálculos de las funciones.

Como regla general, una variable es estática cuando se crea fuera de una función y es dinámica cuando se crea dentro de una función.

Por ejemplo en el siguiente programa:

#include <stdio.h>

int numero1 = 1;

main()
{
 int numero2 = 2;

 printf("%d, %d\n", numero1, numero2);
}

hemos creado la variable estática numero1, que dura todo el programa, y la variable numero2, que dura sólo mientras se ejecuta la función main(). En este programa tan pequeño, la función main() es la que ocupa todo el tiempo de ejecución, por lo que no apreciaremos diferencia en el uso de ambas, aunque más adelante si se verá su uso.

Ámbito o alcance (scope) de las variables

Otra característica de las variables es su alcance. El alcance se refiere a los lugares de un programa en los que podemos utilizar una determinada variable. Distinguiremos así dos tipos principales de variables:
  • globales
  • locales
Una variable es global cuando es accesible desde todo el programa, y es local cuando solo puede acceder a ella la función que la creo. También hay una norma general para el alcance de las variables: una variable es global cuando se define fuera de una función, y es local cuando se define dentro de una función. En nuestro ejemplo anterior numero1 es una variable global y numero2 es una variable local.


Variable GlobalVariable Local
#include <stdio.h>

int x;

main()
{

}
#include <stdio.h>

main()
{
 int x;
}

Dentro de las variables globales hay dos tipos: las que son accesibles por todos los ficheros que componen nuestro programa y las que son accesibles solo por todas las funciones que componen un fichero. Esto es debido a que normalmente los programas en C se fragmentan en módulos más pequeños, que son mas fáciles de manejar y depurar. Por ello hay veces que nos interesar que una variable sea accesible desde todos los módulos, y otras solo queremos que sea accesible por las funciones que componen un determinado modulo. Por defecto todas las variables globales que creemos son accesibles por todos los ficheros que componen nuestro programa.

Especificadores de clase de almacenamiento y modificadores de acceso

Podemos fácilmente modificar el alcance y la duración de una variable que tiene asignado por defecto: Esto es una operación muy común y útil. Para hacerlo antepondremos al tipo de la variable un especificador, que es una palabra reservada, que cambia estas característica:

especificador(o modificador) tipo identificador

El primero es la palabra clave static. Cuando a una variable local se le añade static pasa de ser dinámica a ser estática. Así la duración de la variable se amplía a la duración del programa completo. Obsérvese que una variable estática sólo se crea una vez, al principio del programa, por lo que la inicialización solo se produce una vez. Se dice que tienen memoria asignada durante toda la ejecución del programa. Su valor es recordado incluso si la función donde está definida acaba y se vuelve a llamar más tarde. Ejemplo:

series (void)
{
 static int num;

 num = num + 23;

 return (num);
}

Además, el especificador static tiene otro uso. Si se lo añadimos a una variable global, definida fuera de una función, entonces modificamos su alcance: pasa de tener alcance global a todos los ficheros del programa a ser solo accesible por las funciones del fichero en el que se crea.

Otro especificador usual es extern. Éste se usa cuando una variable que se creó en otro módulo se quiere usar en el actual. Cuando añadimos a la variable este especificador el compilador queda advertido de que la variable ya existe en otro módulo, por lo que el compilador no tiene que crearla, sino simplemente usarla. Entonces a este tipo de proceso se le llama declaración de tipo de variable. Por ejemplo:

extern int numero;

main()
{
 printf("%d\n", numero);
}

es un programa en el que declaramos la variable externa numero, que habremos creado en otro módulo.

Una diferencia muy importante entre una definición y una declaración es que en la declaración no se reserva espacio en la memoria para la variable, y en la definición si se crea.

Hay otros especificadores que no son tan usuales: auto y register. El modificador auto indica que una variable local es dinámica (en la terminología del C, automática). Observar que por defecto las variables locales a una función son automáticas, por lo que no se usa. Sin embargo todos los compiladores la reconocen y no protestan si la usamos. Por ejemplo:

main()
{
 auto numero = 1;

 printf("%d\n", numero);
}

crea una variable automática entera. Si quitamos auto el programa no se diferencia.

El especificador register se usa más a menudo, sobre todo en la llamada "programación de sistemas". Recordemos que el C fue creado para este tipo de programación. Le pide al compilador que almacene la variable en un registro de la máquina, que es el lugar más eficiente para guardar las variables. Esto se hace porque el trabajo con los registros del procesador es mucho más rápido que el trabajo con la memoria central. Hay dos detalles importantes: normalmente no hay muchos registros libres en el procesador, pues el compilador los usa para otros propósitos. Entonces el especificador register es mas bien un ruego que una orden. Otro aspecto es que muchos compiladores realizan trabajos de optimización, que son modificaciones en el código que generan que hace trabajar al programa más deprisa. Aun así, en rutinas críticas, que deben ejecutarse deprisa se suele usar. Sólo se puede aplicar a variables locales y a los parámetros formales de una función. Son ideales para el control de bucles.

potencia_ent (int m, register int e)
{
 register int temp;

 temp=1;
 for ( ; e; e--) temp *= m;

 return (temp);
}

El modificador volatile se usa para decirle que el contenido de la variable puede ser modificado en cualquier momento desde el exterior de nuestro programa, sin que podamos evitarlo. Lo que hace el modificador es instruir al compilador para que lea de nuevo el valor de la variable de la memoria cuando tenga que usarlo. Este modificador evita que el compilador no genere código para optimizar la variable. Evidentemente el uso de volatile excluye el uso de register y viceversa.

El modificador const se usa para indicar que el contenido de la variable no puede ser cambiado durante la ejecución del programa. El compilador es libre de situar las variables de este tipo en memoria de sólo lectura (ROM). Por ejemplo:

 const int a;

crea la variable entera a, que no puede ser modificada por el programa pero sí ser usada en otros tipos de expresiones.

0 comentarios:

Publicar un comentario

Aprende a Programar tus propias aplicaciones