|
AMIGA.InFo • Mayo/Junio 1995 • Número 3
|
|
| Expresiones en C: Parte 1 |
 |
 |
 |
|
A.Moreno
NOTA: Pulsando sobre las fotos con borde azul ampliarás la imagen. |
DECLARACIONES Y
TIPOS DE DATOS
De igual forma que un arquitecto necesita conocer perfectamente
cómo manejar un ladrillo para poder diseñar un gran rascacielos, un
programador necesita conocer absolutamente todas las
características de sus herramientas esenciales. En este caso, las
Variables.
Por eso, este mes vamos a ver cómo podemos trabajar desde C con
ellas. Aprenderemos qué variables hay que utilizar en cada caso ya
que, si utilizamos los tipos de variables adecuados, aseguraremos
que el programa se ejecutará lo más eficientemente posible. Pero
además, si las declaramos en el sitio adecuado, también estaremos
ahorrando memoria y recursos del ordenador. |
En general, los datos se pueden representar utilizando variables o constantes. El C, como la mayoría de los lenguajes de programación, soporta varios tipos diferentes de datos. Unos más simples y otros algo más complicados, pero que, combinándolos entre sí, nos ofrecen una gran flexibilidad a la hora de diseñar la estructura de datos de nuestros programas.
Siempre que se habla de estructuras de datos se suele hacer una división. Se llaman estáticas las estructuras de datos (simples o complejas, da igual), que una vez definidas permanecen sin poder variar su posición en memoria, ni su longitud en bytes, durante la ejecución del programa. Por ejemplo, son estructuras estáticas:
- Las variables de tipo carácter.
- Las variables numéricas enteras.
- Las variables numéricas de punto flotante o reales.
- Las variables de secuencias de caracteres o strings.
- Las matrices o arrays, formadas por elementos contiguos en memoria de cualquiera de los tipos de datos anteriores.
- Los registros y archivos.
Se llaman dinámicas las estructuras de pila, cola, árbol, etc... ya que permiten variar el número de elementos durante la ejecución del programa. De momento, dejamos para más adelante el estudio de estas estructuras, ya que requieren un alto dominio de las estructuras estáticas para llegar a comprenderlas completamente. Así que, de momento, empecemos a estudiar las estructuras estáticas.
Estructuras Simples: Variables
Todos los programas trabajan con datos. Cualquier información con la que trabaje un programa es un dato que se ha de guardar en una zona de memoria, llamada variable. Las variables se identifican por su nombre (que el Sistema Operativo relaciona con una dirección de memoria), su longitud (número de bytes que ocupan), y el tipo de datos que son capaces de guardar.
En C, existen unos cinco tipos de datos básicos: carácter, entero, punto flotante, punto flotante de doble precisión y sin valor (char, int, float, double y void, respectivamente). A partir de estos cinco se podrán construir el resto de tipos de datos del C.
Un dato que hay que tener en cuenta cuando se programa pensando en transportar el código entre varias plataformas es que, el tamaño y el rango de estos tipos de datos varían con cada tipo de procesador y con la implementación del compilador de C. Normalmente, un entero ocupa dos bytes en una máquina con arquitectura de 16 bits (como es la de los PC), y ocupa cuatro bytes en una máquina con arquitectura de 32 bits (como es la del Amiga). Por lo tanto, nunca se deben asumir estas cosas si se quiere que los programas sean portables a otras máquinas. Hay que consultar el manual del compilador para ver exactamente en qué rango de valores nos podemos mover y qué tipo de variables podemos utilizar.
El formato exacto de los valores en punto flotante depende de cómo se implementen. Los enteros, generalmente corresponden al tamaño natural de una palabra del ordenador (En Amiga y MAC 4 bytes, y en PC 2 Bytes). Los valores del tipo carácter (char) se usan normalmente para guardar valores definidos en el juego de caracteres ASCII. Los valores que estén fuera de ese rango puede que sean manipulados de forma diferente por cada compilador de C.
El rango de los tipos float y double, normalmente se da en dígitos de precisión. Las magnitudes de estos tipos dependen del método utilizado para re-presentar los números en punto flotante. Cualquiera que sea el método, los números son bastante grandes.
El tipo void, o bien indica que una función no devuelve ningún valor, o bien crea punteros genéricos (punteros que son capaces de apuntar a cualquier tipo de dato).
Según esto, parece que cada compilador sea un mundo aparte, pero en realidad, no es así. La verdad es que la mayoría de compiladores se asemejan muchísimo y únicamente tienen pequeños detalles en los que difeiren. En la Tabla 1 puedes encontrar una lista de todos los tipos de datos definidos por el estándar ANSI para una máquina con arquitectura de 32 bits, como puede ser la del Amiga.
| Tipo de variable |
 |
Palabra clave en C |
 |
Bytes (32bits) |
 |
Rango |
 |
Posible aplicación |
 |
| carácter |
 |
char |
 |
1 |
 |
-128 a 127 |
 |
Para números muy pequeños y caracteres ASCII. |
| entero |
int |
4 (x) |
-2,147,483,648 a 2,147,483,647 |
Para contadores, números en general y controles de bucles. |
| entero corto |
long |
2 |
-32,768 a 32,767 |
Para contadores, pequeños números y controles de bucles. |
| entero largo |
unsigned char |
1 |
-2,147,483,648 a 2,147,483,647 |
Para números grandes. |
| carácter sin signo |
unsigned int |
4 (x) |
0 a 255 |
Para hacer referencia al set completo de caracteres de Amiga. |
| entero sin signo |
unsigned int |
4 (X) |
0 a 4,294,967,295 |
Para grandes números y controles de bucles. |
| entero corto sin signo |
unsigned short |
2 |
0 a 65,535 |
Para controles de bucles. |
| entero largo sin signo |
unsigned long |
4 |
0 a 4,294,967,295 |
Para el cálculo de distancias astronómicas, por ejemplo. |
| punto flotante de precisión sencilla |
float |
4 |
± 10E-37 a ± 10E+38 |
Para cálculos científicos (Precisión 7 dígitos). |
| punto flotante de doble precisión |
double |
8 |
± 10E-307 a ± 10E-308 |
Para cálculos financieros (Precisión 15 dígitos). |
|
|
Tabla 1: Lista de todos los tipos de datos definidos por el estándar ANSI para una máquina con arquitectura de 32 bits, como puede ser el Amiga. Estos datos difieren entre diferentes plataformas y compiladores de C, así que, id con mucho ojo cuando programéis pensando en transportar vuestro código a otras máquinas (hay que utilizar el operador sizeof() en lugar de suponer los posibles tamaños).
(x) Este valor corresponde a los compiladores Lattice, SAS C, GNU CC y Dice, ya que, en el Aztec C un entero ocupa 2 bytes en lugar de 4. |
Esta tabla la hemos construido empleando un programa, un poco más complicado que el del listado llamado TamanyoDatos.C, pero que sigue la misma filosofía de preguntarle al compilador cuánto ocupan los diferentes tipos de datos (a través del operador sizeof()). Puedes compilar el fuente en tu compilador de C y ejecutarlo para ver qué valores puedes usar.
| TamanyoDatos.C : Programa para calcular el número de bytes que ocupa cada tipo de datos. |
#include <stdio.h>
main()
{
printf("Un char ocupa %d bytes.", sizeof(char));
printf("Un int ocupa %d bytes.", sizeof(int));
printf("Un shor ocupa %d bytes.", sizeof(short));
printf("Un long ocupa %d bytes.", sizeof(long));
printf("Un Unsigned char ocupa %d bytes.", sizeof(unsigned char));
printf("Un Unsigned int ocupa %d bytes.", sizeof(unsigned int));
printf("Un Unsigned short ocupa &d; bytes.", sizeof(unsigned short));
printf("Un Unsigned long ocupa %d bytes.", sizeof(unsigned long));
printf("Un float ocupa %d bytes.", sizeof(float));
printf("Un double ocupa %d bytes.", sizeof(double));
return (0);
} |
Modificadores
Acabamos de ver que hay unos cinco tipos de datos básicos pero, en la tabla 1, podemos ver bastantes más. Entonces, ¿qué ocurre?. Pues bien, a excepción del tipo void, todos los demás pueden llevar delante un modificador para hacer que el rango de valores con los que puede trabajar se ajuste mejor a nuestras necesidades. Veamos cuál es la lista de modificadores:
- signed
- unsigned
- long
- short
Los modificadores signed, unsigned, long y short se pueden aplicar a los tipos enteros y carácter. Sin embargo, long también se puede aplicar a double. Notad que no se usa long float porque tiene el mismo significado que double.
El uso de signed con enteros es redudante ya que, al declarar un entero, asume por defecto que es de números con signo. El uso más importante de signed es para modificar char y poder referirnos al set completo de caracteres de un ordenador.
Algunos compiladores de C permiten aplicar unsigned a los tipos en punto flotante (como unsigned double). Sin embargo, su uso reduce la portabilidad del código y no es recomendable.
La diferencia entre enteros con y sin signo está en cómo se interpreta el bit más significativo del entero. Si se especifica un entero con signo, el compilador de C genera código que asume que el bit más significativo va a ser utilizado como un indicador de signo. Si el indicador de signo es 0, entonces el número es positivo; si es un 1, el número es negativo.
Nombres de Variables
En el estándar ANSI C, los nombres usados para referenciar las variables, las funciones, las etiquetas y otros objetos definidos por el usuario se conocen como identificadores. Cada lenguaje de programación se ajusta a sus propias reglas. Por ejemplo, en C siempre debemos respetar las siguientes:
-
El nombre de la variable puede contener letras, dígitos y el carácter de subrayado "_".
-
El primer carácter del nombre de la variable siempre ha de ser una letra. Esto es importante ya que, si empezamos con un dígito, el compilador nos dará un error. El carácter de subrayado también es un carácter inicial aceptado, aunque no os recomendamos que lo utilicéis ya que puede ocasionaros algún que otro problema con el enlazador.
-
Se hace distinción entre las mayúsculas y minúsculas. Es decir, los nombres saldo, Saldo y SALDO hacen referencia a tres variables diferentes.
-
No se admiten los nombres de variables con espacios en blanco en el medio.
-
Las palabras claves del C no pueden usarse como nombres de variable. Una palabra clave es una palabra que forma parte del lenguaje C. Podéis ver una lista completa de las 33 palabras claves del C en la Tabla 2.
| Palabra clave |
Descripción |
 |
Palabra clave |
Descripción |
 |
sam |
Indica que a continuación viene código escrito en lenguaje Ensamblador. |
 |
if |
Comando que cambia el flujo del programa basándose en una decisión del tipo cierto/falso. |
auto |
Clase de almacenamiento por defecto. |
int |
Tipo de datos para guardar valores enteros. |
break |
Comando que produce la salida incondicional de las instrucciones for, while, switch, do...while. |
long |
Tipo de datos para guardar valores enteros más grandes que int. |
case |
Comando usado con la instrucción switch. |
register |
Clase de almacenamiento que indica que una variable debe ser guardada en un registro. |
char |
El tipo de datos más simple: un carácter. |
return |
Comando que hace que el flujo del programa salga de la función actual y regrese a la función que la llamó. también se usa para regresar un solo valor. |
const |
Se utiliza para informar al compilador de que el valor de la variable no puede ser cambiado por ninguna sentencia del programa. Sin embargo, debe ser inicializado. |
short |
Tipo de datos para guardar enteros. Normalmente no se usa, y es del mismo tamaño que un int en la mayoría de máquinas. |
continue |
Comando que provoca la siguiente iteración de una instrucción for, while o do...while. |
signed |
Indica que una variable tiene valores tanto positivos como negativos. |
default |
Comando usado con la instrucción switch para atrapar cualquier instancia no especifica dentro de un enunciado case. |
sizeof |
Operador de C que indica el tamaño en bytes que ocupa el parámetro solicitado. |
do |
Comando para hacer bucles, usado junto con la instrucción while. El bucle siempre se ejecutará por lo menos una vez. |
static |
Indica que el compilador debe guardar el valor de la variable. |
double |
Tipo de datos que puede guardar valores de punto flotante de doble precisión. |
struct |
Se utiliza para combinar, en un único grupo, variables de cualquier tipo. |
else |
Indica instrucciones alternativas que deberán ser ejecutadas cuando un enunciado if evalúe a falso. |
switch |
Comando que sirve para cambiar el flujo del programa en varias direcciones posibles. Se emplea junto con la instrucción case. |
enum |
Tipo de datos que permite que sean declaradas variables que aceptan solamente determinados valores. |
typedef |
Se utiliza para crear nuevos nombres para tipos de variables y funciones, ya existentes. |
extern |
Indica que una variable será declarada en otra parte del programa. |
union |
Permite que varias variables compartan el mismo espacio de memoria. |
float |
Tipo de datos que puede guardar valores de punto flotante. |
unsigned |
Indica que una variable tendrá solamente valores positivos. |
for |
Comando para hacer bucles, que contiene secciones de inicialización, de condición y de incremento. |
void |
Indica que una función no regresa nada o que un puntero en uso es capaz de apuntar a cualquier tipo de dato. |
goto |
Provoca un salto a una etiqueta predefinida. Normalmente no se usa. |
volatile |
Indica que una variable puede ser cambiada. |
|
|
while |
Comando para hacer bucles, que ejecuta una sección de código mientras una condición se mantenga cierta. |
|
|
| Tabla 2: Lista de las 33 palabras claves del estándar ANSI-C. El estándar ha definido una versión de C ligeramente diferente y ampliada en comparación con el estándar que definieron Brian Kernighan y Dennis Ritchie en su libro "El lenguaje de programación C". En esencia, el estándar ANSI elimina la palabra clave entry que no se utiliza y que había sido previamente reservada. Además, ha añadido cinco palabras clave: const, enum, signed, void y volatile. |
Además, un indicador no debe tener el mismo nombre que alguna función ya escrita o que se encuentre en la biblioteca de C.
Ahora bien, cada programador suele seguir un estilo propio. Por lo general, se suele usar solamente minúsculas en los nombres de variables y mayúsculas en los nombres de constantes, aunque no es obligatorio.
El estándar ANSI obliga a que el compilador tome en cuenta hasta los 31 primeros caracteres del nombre pero, de hecho, pueden ser más largos. Esta flexibilidad es importante que la aprovechemos ya que nos permite crear nombres de variables que reflejen fielmente los datos que guardan. No nos cansaremos de repetiros que, aunque puede llevar algo más de tiempo teclear nombres de variables descriptivos, la mejora que se consigue en claridad hace que valga la pena. Una variable llamada edad_usuario dice mucho más que si se llamase numerito.
Se siguen varios estilos para los nombres de variables creados con varias palabras. Uno de ellos lo acabas de ver: edad_usuario. Lo cierto es que al usar el carácter de subrayado, se consigue facilitar la lectura. Otro estilo es la notación de camello. En vez de edad_usuario, la variable sería nombrada EdadUsuario. Actualmente está ganando popularidad ya que es más fácil teclear una letra mayúscula que un subrayado. Es decisión tuya adoptar uno de los dos estilos.
Veamos una muestra de algunos nombres de identificadores correctos e incorrectos.
| Correcto |
 |
Incorrecto |
 |
| contador |
 |
1contador |
| contador1 |
contador1! |
| cont_inicial |
cont-inicial |
| _mifuncion |
case |
Siempre que queramos utilizar una variable en C, tenemos que declararla antes de usarla. La forma general de declarar cualquier variable es:
tipo lista_de_variables;
Donde tipo es uno de los tipos de datos válidos de C (ver Tabla 1) y lista_de_variables puede consistir en uno o más nombres de identificadores separados por comas. Veamos algunos ejemplos que muestran formas correcta de declarar una variable:
int x,y,z;
short int contador;
unsigned int distancia;
double perdida, beneficio; |
Recordad que en C, el nombre de una variable no tiene nada que ver con su tipo, por lo tanto, siempre podemos poner el nombre que más nos plaza, aunque os aconsejamos, una vez más, que siempre utilicéis nombres significativos. Insistimos mucho en esto, pero es que es bastante importante. Sobre todo cuando hace ya un par de meses que has programado una función y ahora tienes que ponerte a modificar la parte más delicada de ésta... por supuesto, no te acordarás de nada y tendrás que mirarla por completo.
|
Nos han llegado muchas cartas preguntando qué libros aconsejamos para aprender C conjuntamente con el curso de C de AMIGA.InFo.
La bibliografía existente sobre C es verdaderamente extensa, pero intentaremos comentar algunos libros para que os sirva de orientación a la hora de escoger uno de ellos. Os recomendamos que leáis algunos de los libros y artículos que se mencionarán en esta bibliografía, Es divertido y educativo "oír" las ideas de boca de los creadores, y no de las nuestras.
En este caso nos encontramos ante todo un clásico de la programación en C, casi se podría decir que es el primer libro de todo programador de C. El Kerninghan Ritchie, que es como vulgarmente se conoce este libro en el ambiente universitario, ha sido el libro de consulta más utilizado por cientos de estudiantes y programadores de C de todo el mundo. Desde su aparición en ABril del 85 han cambiado muchísimo todos los conceptos relacionados con la programación en C, pero este libro es importante por su contenido e historia y se ha convertido en un verdadero clásico.
Es probable que muchos de vosotros penséis que siendo un libro antiguo, y habiendo surgido tantas versiones de C después de este libro, éste carece de interés. Pero no es así, ya que, sobre todo para los que quieren clarificar sus conceptos sobre el C, o para todos aquellos que estén empezando a conocerlo, éste es un libro imprescindible, tanto por su simplicidad y claridad con que se explican los principios y fundamentos básicos del lenguaje C, como por su gran cantidad de pequeños ejemplos.
Autores: Brian W. Kerninghan y Dennis M. Ritchie. Segunda Edición.
Edita: Prentice Hall.
I.S.B.N.: 968-880-205-0.
Páginas: 294.
Idioma: Castellano.
VALORACIÓN (1-100%): 75%
Podéis encontrarlo en cualquier librería especializada en temas informáticos, o bien, podéis pedirlo directamente a nosotros, a través de nuestros números de teléfono.
NOTA ENCANTA: Los números de teléfono han sido omitidos por tratarse de datos muy antiguos. |
Dónde podemos declarar las variables
Una vez hemos diseñado el algoritmo y ya sabemos cuantas variables necesitaremos, es fundamental escoger el tipo de variable más adecuado. Si utilizamos los tipos de variables numéricas adecuados, aseguraremos que el programa se ejecutará de la forma más veloz posible, ya que, las operaciones matemáticas con enteros no tardan el mismo tiempo que las operaciones en punto flotante, por poner un ejemplo. Pero además, si las declaramos en el sitio adecuado, también estaremos ahorrando memoria y recursos del ordenador. Por lo tanto, es imprescindible saber qué variables utilizar en cada caso y dónde declararla.
Existen tres sitios donde podremos declarar nuestras variables: dentro de las funciones (llamadas variables locales), en la definición de parámetros de funciones (llamadas parámetros formales), y fuera de todas las funciones (llamadas variables globales).
Variables locales
Las variables locales son aquellas que se declaran dentro de una función cualquiera. Hay libros de C donde se hace referencia a este tipo de variables con el nombre de variables automáticas, ya que se puede utilizar la palabra clave auto para declararlas. Este tipo de variables será el que más utilizaremos a lo largo del curso ya que tienen toda una serie de características que nos van a permitir ahorrar recursos de la máquina. A simple vista, por ejemplo, gestionan mejor la cantidad de memoria que gasta nuestro programa, protegen los datos del programa de posibles modificaciones y porciones de código provenientes de una función exterior, etc...
Las variables locales sólo son conocidas por sentencias que estén dentro del bloque en el que han sido declaradas. Es decir, sólo podrán referenciarlas las instrucciones que estén en el mismo bloque que comienza con una llave abierta ({) y termina con una llave cerrada (}). Por lo tanto, las variables locales no son conocidas fuera de su propio bloque de código. Además, sólo existen mientras se está ejecutando el bloque de código en el que son declaradas ya que, la variable local se crea al entrar en el bloque y se destruye al salir de él.
Normalmente, las variables locales se declaran en el interior de las funciones. Por ejemplo, consideremos las dos funciones siguientes:
void funcion1 (void)
{
int x;
x = 5;
}
void funcion2 (void)
{
int x;
x = -10;
} |
La variable entera x se declara dos veces: una vez en funcion1() y otra en funcion2(). Sin embargo, la variable x de la funcion1() no corresponde ni tiene ninguna relación con la variable x de la función2(). Esto es así porque cada x sólo es conocida en la porción de código donde ha sido declarada la variable.
Como ya comentamos al principio de este punto, el C contiene la palabra auto, que puede ser utilizada para declarar variables locales. Sin embargo, cuando declaramos una variable y no la declaramos como variable global, entonces, por defecto, se asume que es una variable local. Por eso casi no se usa nunca la palabra clave auto. Para declarar una variable como local, únicamente hay que declararla dentro de una función y ya está. Aunque, por supuesto, si se quiere, se puede utilizar la palabra auto, ya que para eso está.
La mayoría de los programadores declaran todas las variables que necesitan, para la función, al principio del bloque de código de la misma. Esto se hace así para que se lea de forma más fácil el código. Sin embargo, se pueden declarar variables en cualquier bloque de código. Esta costumbre no se la aconsejamos a nadie, pero si hay alguien que quiera seguir este estilo, vamos a ver un pequeño ejemplo de cómo tiene que hacerlo:
| ERRORES.C : ¡ENCUENTRA LOS 8 ERRORES! |
(* Arregla los errores que encuentres hasta que compile sin dar ningún tipo de error.
Luego intenta explicar cada uno de los diferentes errores, y mándanoslo al apartado
de Correos. *)
#include <studio.h>
main();
{
double cuenta#2, 1ertrimentre, double, numerito;
printf("Saldo disponible: %d \n", numerito));
return (1234567890);
} |
| Función: |
printf() |
| Sintaxis |
#include <stdio.h>
int printf (cadena de control, expresion1, expresion2, ...);
|
| Descripción: |
La función printf() escribe en la salida estándar los argumentos que componen la lista de argumentos. La cadena de control (entre comillas) está formada por dos tipos de elementos. El primero de ellos es la serie de caracteres que se demostrarán literalmente por la salida estándar. el segundo contiene órdenes que expresan con qué formato se van a mostrar los argumentos (llamados especificaciones de conversión). Una orden de formato comienza siempre con el signo de tanto por ciento (%), y va seguido por el código de formato. Debe haber exactamente el mismo número de especificaciones de conversión como valores indicados detrás de la cadena. Además, las órdenes de formato y los argumentos se emparejaran en orden de forma que si sobra algún argumento, entonces se descarta. La salida estándar puede ser la pantalla, un fichero, etc... Puedes encontrar más información sobre el redireccionamiento de la salida de los mensajes en el manual del Amiga.
|
| Ejemplo: |
printf("Curso de %c, Parte: %d %s","C",4, "Mayo-Junio");
Muestra por pantalla el mensaje: "Curso de C, Parte 4 Mayo-Junio".
|
| Órdenes de Formato: |
| Código |
Formato |
%c |
Carácter. |
%d |
Enteros decimales con signo. |
%i |
Enteros decimales con signo. |
%e |
Notación científica (e minúscula). |
%E |
Notación científica (E mayúscula). |
%f |
Coma flotante. |
%g |
Utiliza el más corto de %e o %f. (Si es %e, usa e minúscula) |
%G |
Utiliza el más corto de %E, o %f. (Si es %E, usa E mayúscula). |
%o |
Octal sin signo. |
%s |
Cadena de caracteres. |
%u |
Enteros decimales sin signo. |
%x |
Hexadecimal sin signo. (Letras minúsculas) |
%X |
Hexadecimal sin signo. (Letras mayúsculas) |
%p |
El argumento asociado debería ser un puntero a un entero en el cual se guarda el número de caracteres escritos hasta el momento. |
%% |
Imprime el símbolo %. |
| Secuencias de Escape que se pueden usar |
\n |
Provoca un salto de línea. |
\t |
Tabulador horizontal hacia la derecha. |
\a |
Campana (Alerta). |
\\ |
Imprime la Diagonal inversal \. |
\b |
Retroceso. |
\? |
Signo de Interrogación |
\' |
Comilla simple. |
\" |
Comilla doble. |
|
| A tener en cuenta: |
La función printf() siempre nos devuelve el número de caracteres que ha podido imprimir realmente. Un valor negativo indica que ha ocurrido algún tipo de error. Por lo tanto, sería buena costumbre mirar si se ha producido algún error pero, la verdad, e que nadie lo comprueba nunca.
Las órdenes de formato pueden tener modificadores, tras el símbolo de %, que indiquen la anchura de campo, el número de posiciones de decimales (la precisión), un indicador de justificación a la izquierda o a la derecha y si el entero es un long o un short. Un entero situado entre el símbolo % y la orden de formato, actúa como indicador de la anchura mínima del campo. Esto hace que se rellene la salida con blancos o ceros para asegurar que se alcanza la anchura mínima. Si la cadena o el número es mayor que ese mínimo, será mostrado de forma íntegra incluso si sobrepasa dicho límite. Por defecto, se rellena con espacios en blanco. Si queremos rellenar con ceros, pondremos un cero antes del especificador de anchura de campo. Por ejemplo, %08d rellenará un número de menos de ocho dígitos con ceros, de forma que su longitud total sea ocho.
Cuando trabajemos con números de punto flotante, indicaremos el número de posiciones decimales con un punto decimal seguido del número de decimales que queremos. Por ejemplo, %10.5f mostrará un número de al menos 10 caracteres de ancho con 5 cifras decimales. Cuando esto se aplica a cadenas o enteros, el número que sigue al punto indica la máxima anchura del campo. Por ejemplo, %2.8s mostrará una cadena que tendrá al menos dos caracteres de longitud, y que no pasará de ocho caracteres. en este caso, si la cadena es mayor de ocho caracteres, se truncan los caracteres sobrantes hasta el final.
Por defecto, toda salida está justificada a la derecha. Si la anchura de campo es mayor que los datos mostrados, los datos se situarán en la parte derecha del campo. Se puede hacer que la información quede justificada a la izquierda situando un signo menos directamente después del %. Por ejemplo: %-8.3f justificará a la izquierda un número en punto flotante con tres posiciones decimales en un campo de ocho caracteres.
Existen dos modificadores de órdenes de formato que permiten a printf() mostrar enteros de tipo short y long. Estos modificadores pueden aplicarse a los especificadores de tipo d, y, o, u y x. El modificador / indica a printf() que a continuación sigue un tipo de datos long. Por ejemplo, %ld significa que se va a presentar un long int. El modificador h hace que printf() muestre un short int. De este modo, %hu indica que el dato es del tipo short unsigned int.
El modificador l puede también ser prefijo de las órdenes de punto flotante e, f y g, indicando que los datos son de tipo double. Para mostrar un long double se puede utilizar el prefijo %L.
La orden %n da lugar a que el número de caracteres que se han escrito hasta el momento de encontrar el %n se guarden en una variable entera cuyo puntero se especifica en la lista de argumentos.
El símbolo # tiene un significado especial cuando se utiliza con algunos códigos de formato de printf(). Si se antepone # a los códigos g, f o e, se asegura que la coma decimal aparecerá incluso si no hay cifras decimales. si se antepone # al código de formato x, el número hexadecimal será presentado con el prefijo 0x. Si se antepone # al código de formato o, se presenta el valor octal con el prefijo 0. El símbolo # no puede ser aplicado a otros especificadores de formato. |
|
#include <stdio.h>
void funcion(void)
{
int x;
scanf("%d", &t;);
if (t==0)
{
char buffer[20];
/* esta variable se crea sólo
al entrar en este bloque */
printf("Introduce el nombre: ");
gets(buffer);
...
}
} |
Aquí, la variable local buffer se crea tras entrar en el bloque de código if y se destruye tras salir de él. Más aún, buffer sólo es conocida dentro del bloque if y no puede ser referenciada en ninguna parte fuera de él. Ni siquiera en otras partes de la función que la contiene. A esto nos referíamos cuando comentábamos que las variables locales nos proporcionaban una cierta protección frente a posibles modificaciones accidentales.
Otra de las ventajas de declarar una variable local en un bloque condicional es que sólo se dispondrá la memoria necesaria para ella si se necesita. Esto se debe a que las variables locales no existen hasta que se entra en el bloque en el que están declaradas. Y por supuesto, sólo se entrará en un bloque condicional cuando se cumpla la condición, es decir, justo cuando de verdad necesitemos utilizar otra nueva variable.
Ahora bien, como la variable no existe fuera del bloque en el que está declarada, no puede ser accidentalmente modificada. Sin embargo, cuando una función lleva a cabo una tarea bien definida y sobretodo, bien programada, no es necesario "proteger" las variables internas de la función. Por eso, se declaran normalmente todas las variables usadas por la función al principio de la misma.
Dado que las variables locales se crean y se destruyen cada vez que se entra o se sale del bloque en el que son declaradas, su contenido se pierde tras salir de éste. Es especialmente importante recordar esto por lo que respecta a las llamadas de funciones. Cuando se llama a una función, se crean sus variables locales y cuando se sale de ella se destruyen. Esto significa que las variables locales no pueden retener sus valores entre llamadas (sin embargo, es posible indicar al compilador que retenga sus valores mediante el uso del modificador static).
A menos que se especifique lo contrario, el compilador se encarga de guardar las variables locales en la pila. La pila es una zona de memoria dinámica que cambia su contenido constantemente. Sobre todo, cada vez que se produce una llamada o un retorno de una función se produce una actualización. Esto explica por qué las variables locales no pueden, en general, mantener sus valores entre llamadas a funciones (ya que es cuando la pila se actualiza con nuevos valores).
Siempre que queramos, podremos inicializar una variable local con algún valor determinado. Cada vez que se entre en el bloque de código en que la variable está declarada, se asignará ese valor a la misma y se continuará con la ejecución.
Parámetros Formales
Siempre que una función va a usar argumentos, debe declarar las variables que van a aceptar los valores de los argumentos. Esas variables son los parámetros formales de la función. Se comportan como cualquier otra variable local de la función y podemos definirla de la siguiente forma:
1ª Forma:
void
Función (a, b, c)
int a;
int b;
int c;
{
... El código sangrado iría aquí
}
2ª Forma:
void
Funcion (a, b, c)
int a;
int b;
int c;
{
...El código sangrado iría aquí
}
3ª Forma:
void
Funcion (int a, int, b, int c)
{
... El código sangrado iría aquí
}
4ª Forma:
void
Funcion{
int a;
int b;
int c;
}{
... El código sangrado iría aquí
} |
Las dos primeras formas usan el viejo estilo de las declaraciones descritas en el libro: "El lenguaje de programación C" de Brian Kernighan y Dennis Ritchie. Mucha gente, y entre ellas me encuentro yo, opina que están pasadas de moda y prefieren el estilo de las declaraciones del estándar ANSI, mostrado en las dos últimas formas. Generalmente, la tercera forma es usada cuando la función tiene pocos argumentos, y la cuarta forma se usa cuando la función tiene muchos argumentos.
No hay que olvidar que, al igual que las variables locales, los parámetros formales también son dinámicos y se destruyen al salir de la función. Por supuesto, hay que asegurarse de que los parámetros formales se declaren del mismo tipo que los argumentos que se usarán en la llamada a la función. Si por un descuido, existe una diferencia de tipo, puede pasar cualquier cosa inesperada (incluso se puede llegar a colgar la máquina).
El estándar ANSI proporciona los llamados prototipos de funciones, que se pueden usar para ayudar en la verificación de la compatibilidad de los argumentos usados en las llamadas a las funciones con parámetros. Sin embargo requieren un poco más de trabajo y lo veremos con detalle en un próximo artículo, cuando hablemos de funciones.
Variables Globales
La principal característica de las variables globales es que, a diferencia de las variables locales, se conocen a lo largo de todo el programa y se pueden usar en cualquier parte del código, nos referimos a eso exactamente. Podemos utilizarla dentro de una función, fuera, como parámetro formal, etc... vamos, donde queramos ya que mantienen su valor durante toda la ejecución del programa.
Para declarar una variable global tenemos que declararla en cualquier parte fuera de una función y antes de su primer uso. la forma más adecuada de declarar las variables globales es declarándolas todas al principio del programa, incluso antes de la función main(). Veamos un ejemplo:
#include <stdio.h>
int x; /* x es global */
void funcion1(void);
void funcion2(void);
void main (void)
{
x = 10;
funcion1();
}
void funcion1 (void)
{
funcion2();
printf("X vale ; %i", x);
/* mostrara X vale : 10 */
}
void funcion3 (void)
{
int x;
x = 25000;
} |
Fijaos que, aunque ni main() ni funcion1() han declarado la variable x, ambas pueden usarla. SIn embargo, la función2() declara una variable local llamada también x. Cuando funcion2() hace referencia a x, se refiere a su variable local, no a la global. Si una variable global y una variable local tienen el mismo nombre, todas las referencias a ese nombre de variable, dentro de la función donde se ha declarado la variable local, se refieren a esa variable local y no tienen efecto sobre la variable global, como ha ocurrido en el ejemplo anterior. Esto puede ser alguna vez conveniente, pero si se olvida, el programa puede actuar de forma extraña, incluso aunque parezca correcto.
El almacenamiento de las variables globales se hace en una región de memoria fija establecida por el compilador. Utilizan memoria todo el tiempo de ejecución del programa, no sólo cuando se necesitan. Además, el uso de una variable global cuando se podría usar una variable local hace que la función sea menos general debido a que depende algo que debe estar definido fuera de ella.
Además, uno de los aspectos principales de un lenguaje estructurado es que comparten código y datos. En C se consigue mediante el uso de variables locales y funciones.
| Consultas de los Lectores |
Málaga 17 de Mayo de 1995
Amigos de A.I.: Estoy siguiendo vuestro curso de C y tengo un buen problema. Tengo un A500 con 1 mega de Ram y el compilador Lattice C V5.0. El problema viene cuando introduzco el listado correspondiente a menu.c, una vez compilado lo ejecuto y cual es mi sorpresa al ver que no me han salido los códigos de escape, ninguno. Lo que sí sale es lo siguiente: ESC[1mESC[4m Menú de Amiga.InFo; y así con los demás códigos, es decir, imprime las letras ESC[1m en lugar de poner en negrita las letras, y vuelvo a repetir que pasa igual con todos los demás códigos de los macros.
He probado directamente desde el Shell a poner: Echo"ESC[1m" y lo que hace es imprimirlo. Sin embargo, he puesto: Echo "*e[1m" y sorpresa; funciona, pone en negrita el prompt y todo lo que escriba. He probado a ponerlo en el programa, es decir, cambiando todos los ESC por *e. Pero sigue sin funcionar. espero con una gran impaciencia la solución a este problema. ¿Por qué no me salen los códigos?.
Se despide atentamente:
Juan Manuel Moyano Cosano
Empecemos desde el principio. Para que las secuencias de código de escape fueran, más o menos, compatibles entre diferentes ordenadores se encargó a un comité que desarrollase un estándar. ese estándar es el "ISO Compatible Escape Sequences". en él, se dice que una secuencia de códigos siempre ha de estar introducida por una especie de identificador llamado CSI. Veamos cual es su sintaxis:
<CSI><Parámetros><Letras/s de la secuencia de control>
Este CSI indica que a partir de él encontraremos una secuencia de control. Por definición, el CSI es el valor 155 en decimal ó 0x9B en Hexadecimal. Ahora bien, se puede usar otra combinación de dos caracteres que produce un resultado similar. Se trata de la combinación ESC[(0x1B, 0x5B ó 27, 91 en decimal).
Desde C se pueden definir como:
#define CSI 0x9B
#define ESC[ 0x1B 0x5B
aunque la manera más cómoda de utilizarlos es la siguiente:
Ejemplo: Sabemos que la secuencia ESC[1m pone las letras en negrita, por lo tanto, en el programa en C pondremos:
printf("ESC[1m");
Donde ESC NO son las letras ESC. Cada vez que pongamos ESC en los listados nos referimos a la tecla ESC situada en el extremo superior izquierdo del teclado. Por lo tanto, para corregir el programa, sólo tienes que sustituir cada aparición de las tres letras ESC por una pulsación de la tecla ESC. Cada vez que pulses ésta tecla te saldrá un carácter [ pero con el fondo negro.
Una vez que hayas cambiadotodas las apariciones de ESC, podrás compilar de nuevo y esta vez sí tendrás los resultados esperados.
En uno de los discos de portada encontrarás los listados pertenecientes al curso de C: Uno de ellos se encarga de mostrar por pantalla una serie de códigos de escape para que veáis cómo se pueden aplicar diferentes colores a los textos mostrados por la pantalla.
Hasta otra y que lo disfrutéis.
|
|