Lenguaje C
Lenguaje C
Lenguaje C
Introduccin
1 Introduccin
Breve historia
El Lenguaje C fue creado en 1972 por Dennis Ritchie en un PDP-11 de Digital Equipment Corporation bajo el sistema operativo UNIX. Fue el resultado final de un proyecto que comenz con un lenguaje llamado BCPL (Basic Combined Programming Language) diseado por Martin Richards en 1967, que a su vez estaba influenciado por el lenguaje CPL (Combined Programming Language) desarrollado por las universidades de Cambridge y Londres. A partir del BCPL, Ken Thompson cre un lenguaje llamado B, que fue el que condujo al desarrollo del Lenguaje C. Durante muchos aos el estndar para C fue el que se suministraba con la versin 5 de UNIX. Pero con la creciente popularidad de los microordenadores aparecieron muchas implementaciones diferentes (Quick C de Microsoft, Turbo C de Borland, etc.) que, aunque eran altamente compatibles entre s, tenan algunas diferencias. Por ello, en 1983 se cre un comit que elabor el documento que define el estndar ANSI de C. El Lenguaje C es un lenguaje de nivel medio, es decir, sin ser un lenguaje de alto nivel como COBOL, BASIC o Pascal, tampoco es un Lenguaje Ensamblador. Las principales caractersticas del Lenguaje C son: Tiene un conjunto completo de instrucciones de control. Permite la agrupacin de instrucciones. Incluye el concepto de puntero (variable que contiene la direccin de otra variable). Los argumentos de las funciones se transfieren por su valor. Por ello, cualquier cambio en el valor de un parmetro dentro de una funcin no afecta al valor de la variable fuera de ella. La E/S no forma parte del lenguaje, sino que se proporciona a travs de una biblioteca de funciones. Permite la separacin de un programa en mdulos que admiten compilacin independiente.
Introduccin al Lenguaje C
Originalmente el Lenguaje C estuvo muy ligado al sistema operativo UNIX que, en su mayor parte, est escrito en C. Ms adelante se comenz a utilizar en otros sistemas operativos para programar editores, compiladores, etc. Aunque se le conoce como un lenguaje de programacin de sistemas, no se adapta mal al resto de aplicaciones. De hecho, hoy en da un alto porcentaje de software para ordenadores personales est escrito en Lenguaje C. Por ejemplo, el sistema operativo MS-DOS. En este captulo realizaremos un rpido recorrido por algunas de las caractersticas del lenguaje a travs de unos ejemplos muy sencillos. En los siguientes captulos estudiaremos con mucho ms detalle la mayor parte de los aspectos del Lenguaje C. Este estudio lo basaremos en la implementacin de Borland: el Turbo C. Estos programas pueden ejecutarse desde el entorno integrado de Turbo C o compilndolos y enlazndolos desde la lnea de rdenes del DOS (Captulo 12).
/* Ejemplo 1. Programa DOCENA.C */ #include <stdio.h> main () { int docena; docena = 12; printf ("Una docena son %d unidades\n", docena); }
Este programa hace aparecer en pantalla la frase "Una docena son 12 unidades". Veamos el significado de cada una de las lneas del programa.
1. Introduccin
#include <stdio.h>
Le dice a Turbo C que en el proceso de compilacin incluya un archivo denominado stdio.h. Este fichero se suministra como parte del compilador de Turbo C y contiene la informacin necesaria para el correcto funcionamiento de la E/S de datos. La sentencia #include no es una instruccin C. El smbolo # la identifica como una directiva, es decir, una orden para el preprocesador de C, responsable de realizar ciertas tareas previas a la compilacin. Los archivo *.h se denominan archivos de cabecera. Todos los programas C requieren la inclusin de uno o varios archivos de este tipo, por lo que normalmente es necesario utilizar varias lneas #include.
main ()
Es el nombre de una funcin. Un programa C se compone de una o ms funciones, pero al menos una de ellas debe llamarse main(), pues los programas C empiezan a ejecutarse por esta funcin. Los parntesis identifican a main() como una funcin. Generalmente, dentro de ellos se incluye informacin que se enva a la funcin. En este caso no hay traspaso de informacin por lo que no hay nadaescrito en su interior. An as son obligatorios. El cuerpo de una funcin (conjunto de sentencias que la componen) va enmarcado entre llaves { y }. Ese es el significado de las llaves que aparecen en el ejemplo.
int docena;
Es una sentencia declarativa. Indica que se va a utilizar una variable llamada docena que es de tipo entero. La palabra int es una palabra clave de C que identifica uno de los tipos bsicos de datos que estudiaremos en el Captulo 3. En C es obligatorio declarar todas las variables antes de ser utilizadas. El ";" identifica la lnea como una sentencia C.
docena = 12;
Es una sentencia de asignacin. Almacena el valor 12 a la variable docena. Obsrvese que acaba con punto y coma. Como en la mayora de los lenguajes, el operador de asignacin en C es el signo igual "=".
Introduccin al Lenguaje C
Ejemplo 2: scanf()
El siguiente programa realiza la conversin de pies a metros usando la equivalencia: 1 pie = 0.3084 metros El programa solicita por teclado el nmero de pies y visualiza en pantalla los metros correspondientes.
1. Introduccin
/* Ejemplo 2. Programa PIES.C */ #include <stdio.h> main () { int pies; float metros; printf ("\nPies?: "); scanf ("%d", &pies); metros = pies * 0.3084; printf ("\n%d pies equivalen a %f metros\n", pies, metros); }
float metros;
Es una sentencia declarativa que indica que se va a utilizar una variable llamada metros, que es del tipo float. Este tipo de dato se utiliza para declarar variables numricas que pueden tener decimales.
Introduccin al Lenguaje C
/* Ejemplo 3.1 - Programa FUNCION1.C */ #include <stdio.h> main () { printf ("\nEste mensaje lo muestra la funcin main()"); MiFuncion (); } /* Definicin de la funcin MiFuncion() */ MiFuncion () { printf ("\nEste otro lo muestra MiFuncion()"); }
1. Introduccin
En este ejemplo se utiliza la funcin MiFuncion() para mostrar en pantalla una frase. Como se ve, MiFuncion() se invoca igual que printf() o scanf(), es decir, simplemente se escribe el nombre de la funcin y los parntesis. La definicin de MiFuncion() tiene el mismo aspecto que main(): el nombre de la funcin con los parntesis y, seguidamente, el cuerpo de la funcin encerrado entre llaves. El siguiente ejemplo ilustra el uso de una funcin con argumentos. El programa visualiza el cuadrado de un nmero entero por medio de una funcin que recibe dicho nmero como argumento.
/* Ejemplo 3.2 - Programa FUNCION2.C */ #include <stdio.h> main () { int num; printf ("\nTeclee un nmero entero: "); scanf ("%d", &num); cuadrado (num); } /* Definicin de la funcin cuadrado() */ cuadrado (int x) { printf ("\nEl cuadrado de %d es %d\n", x, x * x); }
cuadrado (int x)
Es la declaracin de la funcin cuadrado(). Dentro de los parntesis se pone la variable que recibir el valor pasado a cuadrado() y de qu tipo es. As, si se teclea el valor 6, se almacena en num y al hacer la llamada cuadrado (num), la variable num se copia en la variable x, que es con la que trabaja internamente la funcin cuadrado(). Es importante mantener claros dos trminos: 1. El trmino ARGUMENTO se refiere a la variable usada al llamar la funcin. 2. El trmino PARMETRO FORMAL se refiere a la variable de una funcin que recibe el valor de los argumentos. Tambin es importante tener claro que la copia de variables se hace slo en una direccin: del argumento al parmetro formal. Cualquier modificacin del parmetro formal realizado dentro de la funcin no tiene ninguna influencia en el argumento.
Introduccin al Lenguaje C
ARGUMENTO
PARMETRO FORMAL
Otro detalle a tener en cuenta es que el tipo de argumento que se utiliza para llamar a una funcin debe ser el mismo que el del parmetro formal que recibe el valor. As, no debe llamarse a la funcin cuadrado con un argumento de tipo float (ms adelante veremos que C permite cierta flexibilidad en este aspecto).
/* Ejemplo 4 - Programa MULT.C */ #include <stdio.h> main () { int a, b, producto; printf ("\nTeclee dos nmeros enteros: "); scanf ("%d %d", &a, &b); producto = multiplica (a, b); printf ("\nEl resultado es %d", producto); } /* Definicin de la funcin multiplica() */ multiplica (int x, int y) { return (x * y); }
1. Introduccin
return (x * y);
La palabra clave return se usa dentro de las funciones para salir de ellas devolviendo un valor. El valor devuelto mediante return es el que asume la funcin. Eso permite tener sentencias como producto = multiplica (a, b); es decir, sentencias en las que la funcin est a la derecha del operador de asignacin. Para nuestros propsitos actuales podemos decir (aunque esto no sea exacto) que despus de la sentencia return la funcin multiplica() acta como si fuese una variable que almacena el valor devuelto. Los parntesis son opcionales, se incluyen nicamente para clarificar la expresin que acompaa a return. No deben confundirse con los parntesis de las funciones.
Ejercicios
1.
Encuentra todos los errores del siguiente programa C:
include studio.h /* Programa que dice cuntos das hay en una semana /* main {} ( int d d := 7; print (Hay d das en una semana);
10
Introduccin al Lenguaje C
2.
Indica cul sera la salida de cada uno de los siguientes grupos de sentencias: a) printf ("Historias de cronopios y famas."); printf ("Autor: Julio Cortzar"); b) printf ("Cuntas lneas \nocupa esto?"); c) printf ("Estamos \naprendiendo /naprogramar en C"); d) int num; num = 2; printf ("%d + %d = %d", num, num, num + num);
3.
Escribe un programa que calcule el rea de un crculo de radio R y la longitud de su circunferencia. Solicitar el valor de R por teclado, mostrando en la pantalla los mensajes necesarios (S = R2 ; L = 2 R).
4.
Sean dos cuadrados de lados L1 y L2 inscritos uno en otro. Calcula el rea de la zona comprendida entre ambos, utilizando para ello una funcin (que se llamar AreaCuadrado) que devuelve el rea de un cuadrado cuyo lado se pasa como argumento.
1. Introduccin
11
2. Elementos de un programa C
Introduccin
Bsicamente el C est compuesto por los siguientes elementos Constantes Identificadores Palabras reservadas Comentarios Operadores
Para representar estos elementos se utilizan los caracteres habituales (letras, nmeros, signos de puntuacin, subrayados, ...) aunque no todos los elementos pueden usar todos estos caracteres. Una caracterstica importante del Lenguaje C es que en todos los elementos anteriormente enumerados distingue letras maysculas y minsculas. As, int es una palabra reservada del lenguaje que sirve para declarar variables enteras, mientras que Int podra ser el nombre de una variable.
Constantes
Las constantes que se pueden usar en C se clasifican de la siguiente forma: Enteras Reales De carcter
Constantes enteras
Son nmeros sin parte fraccionaria. Pueden expresarse en decimal, octal o hexadecimal. Una constante octal debe comenzar con un cero: 016 16 octal = 14 decimal
12
Introduccin al Lenguaje C
Una constante hexadecimal debe comenzar con un cero seguida de x X. 0xA3 A3 hex = 163 decimal Esta constante se puede escribir tambin de cualquiera de las 3 formas siguientes: 0XA3 0xa3 0xA3
Las constantes enteras se consideran positivas a menos que vayan precedidas por el signo menos (-): -150 -063 -0xA
Constantes Reales
Tambin se denominan constantes de coma flotante. Tienen el siguiente formato: [parte entera] [.parte fraccionaria] [exponente de 10] Cualquiera de las 3 partes es opcional, pero si no hay parte entera debe haber parte fraccionaria y viceversa. El exponente de 10 tiene el formato {E|e}exponente pudiendo ser el exponente un nmero positivo o negativo. Son constantes vlidas: 13.21 21.37E1 0.230001 32e2 -81e-8 -.39 -.39E-7
Constantes de caracteres
Pueden ser de 2 tipos: Simples Cadenas de caracteres Simples: Estn formadas por un solo carcter y se encierran entre comillas simples. Por ejemplo: 'a' 'A' '9'
Los caracteres ASCII no imprimibles se definen mediante la barra invertida (\) segn el cuadro que se muestra a continuacin. En l tambin se muestra la representacin de los caracteres barra invertida, comilla simple y comillas dobles, que en C tienen un tratamiento especial.
CARCTER BARRA
SIGNIFICADO
\a \b
1. Introduccin
13
9 10 11 12 13 34 39 92
\t \n \v \f \r \" \' \\
Tabulador Horizontal (HT) Nueva Lnea (LF) Tabulador Vertical (VT) Nueva Pgina (FF) Retorno Comillas dobles Comilla simple Barra invertida
Tambin se pueden representar caracteres ASCII mediante su cdigo octal o hexadecimal, usando el formato: \numoctal o bien \xnumhexadecimal
que representan, respectivamente, el carcter cuyo cdigo ASCII es numoctal o numhexadecimal. As, la letra A puede representarse de cualquiera de las tres formas que se indican a continuacin: 'A' '\101' '\x41'
No es vlido '\X41'. Cualquier otro carcter despus de \ se interpreta literalmente. As \N se interpreta como la letra N. Cadenas: Son secuencias de caracteres simples encerradas entre comillas dobles. A las cadenas de caracteres el compilador les aade un carcter nulo ('\0') de terminacin, y los almacena como una matriz de caracteres. As, la cadena "Hola" est compuesta por los 5 caracteres 'H', 'o', 'l', 'a','\0'.
Identificadores
Son los nombres dados a variables, funciones, etiquetas u otros objetos definidos por el programador. Un identificador puede estar formado por: Letras (maysculas o minsculas) Nmeros Carcter de subrayado con la condicin de que el primer carcter no sea un nmero. En determinados casos, que se estudiarn en el Captulo 9, un identificador de un dato puede incluir el punto. Ejemplos de identificadores vlidos son: Precio_Venta Num1 _123 D_i_5 No son vlidos: Precio Venta 1Num Lleva un espacio en blanco Empieza por un nmero
14
Introduccin al Lenguaje C
Precio-Venta
Lleva un guin
Palabras reservadas
Son palabras especiales que no pueden usarse para nombrar otros elementos del lenguaje. En el captulo anterior vimos algunas de ellas, como int y float. El nmero de palabras reservadas en C es significativamente menor que el de otros lenguajes. En Turbo C hay 43, algunas ms en Turbo C++ y menos en ANSI C. Durante el resto de captulos se irn conociendo. Es preciso insistir en que C hace distincin entre maysculas y minsculas. Por lo tanto, la palabra reservada for no puede escribirse como FOR, pues el compilador no la reconoce como una instruccin, sino que la interpreta como un nombre de variable.
1. Introduccin
15
Comentarios
Como se vio en el captulo anterior, el compilador reconoce como comentario cualquier grupo de caracteres situados entre /* y */, aunque estn en diferentes lneas. Por ejemplo,
Estos comentarios pueden anidarse en Turbo C++, aunque no es aconsejable para permitir la compatibilidad del cdigo. Se pueden definir comentarios de una sola lnea mediante //.
Estudiaremos en este captulo la mayor parte de ellos. Iremos viendo el resto a medida que se necesiten. Los operadores, junto con los operandos, forman expresiones. En una expresin, los operandos pueden ser constantes, variables o llamadas a funciones que devuelvan valores (como la funcin multiplica () que aparece en la pgina 8). Una expresin se convierte en una sentencia cuando va seguida de un punto y coma. Cuando un grupo de sentencias se encierran entre llaves { }, forman un bloque, sintcticamente equivalente a una sentencia.
16
Introduccin al Lenguaje C
Operadores aritmticos
Los operadores aritmticos se exponen en el cuadro siguiente:
DESCRIPCIN Cambio de signo Decremento Incremento Resta Suma Producto Divisin Resto de divisin entera
Los operadores -, + y * funcionan del mismo modo que en el resto de los lenguajes de programacin. El valor devuelto por el operador / depende del tipo de los operandos. Si estos son enteros, devuelve la parte entera del cociente; si alguno de ellos es real, devuelve el resultado como nmero real. El operador % es equivalente al operador mod de Pascal o Quick-BASIC. Proporciona el resto de la divisin entera de los operandos, que han de ser enteros. Por ejemplo, dadas las sentencias int x, y; x = 9; y = 2; la operacin x / y devuelve el valor 4, mientras que la operacin x % y devuelve 1. Sin embargo, despus de las sentencias float x; int y; x = 9.0; y = 2; la operacin x / y devuelve 4.5, no pudindose aplicar, en este caso, el operador % puesto que uno de los operandos no es entero. Los operadores ++ y -- aumentan o disminuyen, respectivamente, en una unidad el operando sobre el que actan. As, las expresiones x++; producen el mismo efecto sobre la variable x que x = x + 1; y el mismo que ++x; --x; x = x - 1; x- -;
1. Introduccin
17
Es importante tener en cuenta la posicin de los operadores ++ y -- cuando se encuentran dentro de una expresin ms compleja. Si el operador est antes de la variable, la operacin de incremento o decremento se realiza antes de usar el valor de la variable. Si el operador est despus, primero se usa el valor de la variable y despus se realiza la operacin de incremento o decremento. Para aclarar esto, tomemos un ejemplo. Despus de las sentencias x = 10; y = ++x; los valores que se almacenan en las variables x e y son, en ambos casos, 11. Puesto que el operador est antes de la variable x, primero se incrementa sta (asumiendo el valor 11), y despus se asigna el valor de x a la variable y. Sin embargo, despus de las sentencias x = 10; y = x++; los valores para x e y sern, respectivamente, 11 y 10. En este caso, puesto que el operador est despus de la variable x, primero se usa el valor de sta (10) y se asigna a y; despus se realiza la operacin de incremento, pasando x a almacenar el valor 11.
Operadores relacionales
Se usan para expresar condiciones y describir una relacin entre dos valores. En la pgina siguiente se muestra una tabla con todos ellos. Estos operadores se usan en sentencias del tipo if (a == b) printf ("Son iguales"); que debe leerse "si el contenido de la variable a es igual al de la variable b muestra en pantalla la frase Son iguales". El resultado de una expresin relacional slo puede ser verdadero o falso, lo que en C se identifica con los valores distinto de cero y cero, respectivamente. En la sentencia anterior, la expresin a == b se evaluar como 0 si a y b son diferentes, y como distinto de 0 si son iguales.
DESCRIPCIN Mayor que Mayor o igual que Menor que Menor o igual que Igual que Diferente que
Operadores lgicos
Actan sobre expresiones booleanas, es decir, sobre valores verdadero o falso generados por expresiones como las explicadas en el caso anterior. Son los siguientes:
18
Introduccin al Lenguaje C
El resultado de una operacin lgica viene dado por su tabla de verdad. La tabla de verdad de los operadores !, && y || se muestra a continuacin:
b F V F V
!a V V F F
a && b F F F V
a || b F V V V
Dado que el lenguaje interpreta como falso el valor 0 y como cierto cualquier valor diferente de cero, se pueden emplear operadores aritmticos en las expresiones lgicas y de comparacin. Por ejemplo, si x, y y z almacenan, respectivamente, los valores 20, 4 y 5, las expresiones siguientes son vlidas: x == y x=y x == (y * z) Se interpreta como FALSO (0) 1 Se interpreta como VERDADERO (4) Se interpreta como VERDADERO (1)
Veremos ms adelante que el operador = devuelve el valor asignado. En este caso, la expresin .x = y devuelve el valor 4, que se evala como cierto.
1. Introduccin
19
Los operadores & (and), | (or) y ~ (not) se rigen por la misma tabla de verdad que gobierna a los operadores lgicos equivalentes (&&, ||, !). La diferencia entre unos y otros consiste en que &, | y ~ actan a nivel de bits individuales y no sobre valores completos como &&, || y !. As, si las variables a y b almacenan, respectivamente, los valores a 0xA1B2 b 0xF0F0 las siguientes expresiones se evalan como sigue: a && b 1 (Verdadero) a || b 1 (Verdadero) !a 0 (Falso) a & b 0xA0B0 a | b 0xF1F2 ~a 0x5E4D
Los operadores &, |, y ~ son idnticos, respectivamente, a las instrucciones del Lenguaje Ensamblador AND, OR y NOT. Por tanto, el operador & permite poner ciertos bits a 0 dejando el resto como estaban, el operador | permite poner ciertos bits a 1 dejando el resto inalterado, y el operador ~ cambia los bits 1 por 0 y viceversa (Ver ejercicio 10 al final del captulo). El operador ^ (or exclusivo) es idntico a la instruccin XOR de Lenguaje Ensamblador. Su tabla de verdad es la siguiente.
a F F V V
b F V F V
a^b F V V F
Por ejemplo, si a y b almacenan los valores a 0xA1B2 b 0x1234 la siguiente expresin produce el resultado indicado a ^ b 0xB386 El operador ^ permite cambiar ciertos bits a su valor contrario, dejando el resto sin modificar (Ver ejercicio 10 al final del captulo). Los operadores de desplazamiento >> y <<, mueven todos los bits de una variable a la derecha o a la izquierda, respectivamente, un nmero de posiciones determinado. El formato general es: variable << n As, la sentencia a = b << 4; almacena en a el contenido de b, despus de realizar un desplazamiento de b de 4 bits a la izquierda. El contenido de b permanece inalterado. o bien variable >> n
20
Introduccin al Lenguaje C
Al igual que en Lenguaje ensamblador, C distingue entre desplazamientos aritmticos y lgicos: Desplazamientos aritmticos: Se realizan sobre datos enteros y mantienen el signo. Desplazamientos lgicos: Se realizan sobre datos declarados como sin signo (unsigned)2 y simplemente aade ceros. Veamos un ejemplo. Sea a una variable declarada como entera (con signo), que almacena el valor hexadecimal A1B2: a 0xA1B2 a = a << 4 a = a >> 4 produce a 0x1B20 produce a 0xFA1B
Sin embargo, si a se declara como unsigned, a = a << 4 a = a >> 4 produce a 0x1B20 produce a 0x0A1B
Operadores de asignacin
Las asignaciones se realizan mediante el operador =. El uso de este operador tiene ciertos aspectos que lo distinguen del de otros lenguajes. En primer lugar, se puede emplear cualquier nmero de veces en una expresin. As, podemos tener sentencias como a = b = c = 3; que asigna el valor 3 a las variables a, b y c. Esto es as porque la operacin de asignacin, adems de asignar el valor, devuelve el valor asignado. As, la expresin c = 3 devuelve el valor 3, que se asigna a b, y as sucesivamente. Tambin son posibles sentencias como x = 3; y = x + (z = 6); que asigna a x, y, y z los valores 3, 9 y 6, respectivamente. El operador de asignacin se combina con los operadores *, /, %, +, -, <<, >>, &, |, ^, para operaciones acumulativas. Por ejemplo,
es equivalente a
unsigned es una palabra reservada que se puede aplicar al tipo de dato int. Las variables as declaradas se entienden siempre como positivas.
1. Introduccin
21
<condicin> ? <expresin_s> : <expresin_no> Funciona del siguiente modo: primero se evala <condicin>; si es verdadera se evala <expresin_s>, en caso contrario se evala <expresin_no>. Por ejemplo, en la sentencia y = (x > 9 ? 100 : 200); la variable y toma el valor 100 cuando x > 9, y el 200 en caso contrario.
El operador & acta sobre una variable y devuelve su direccin de memoria. Despus de dir_a = &a; la variable dir_a almacena la direccin de a. El operador * acta sobre variables que contienen direcciones (punteros) y devuelven el contenido de la posicin de memoria almacenada en la variable sobre la que actan. Si dir_a es una variable que almacena una direccin de memoria, la expresin b = *dir_a; almacena en la variable b el contenido de la posicin de memoria apuntada por dir_a. De igual modo, la expresin *dir_a = 80; almacena el valor 80 en la direccin de memoria apuntada por dir_a.3 Despus de la respectivamente. m = 1; n = 2; direcc = &m; *direcc = 4; direcc = &n; z = *direcc; siguiente secuencia, las variables m y z almacenan los valores 4 y 2
Para que las sentencias en las que interviene la variable dir_a funcionen correctamente, sta debe declararse de un modo especial, que explicaremos en el Captulo 7, en el que se estudiarn los punteros.
22
Introduccin al Lenguaje C
Operadores . y ->
Se estudiarn detenidamente en el Captulo 8, dedicado a uniones y estructuras.
1. Introduccin
23
Operadores [ ] y ( )
Los corchetes se utilizan para acceder a los elementos de una matriz. Se estudiarn en el Captulo 7. Los parntesis sirven para clarificar una expresin o para modificar las reglas de prioridad entre operadores. Estas reglas de prioridad quedan reflejadas en la tabla de la pgina siguiente. Las prioridades de esta tabla pueden alterarse por medio de los parntesis. Cuando alguna expresin se enmarca entre parntesis, se evala primero. Si hay anidamiento, se evalan primero los ms internos. Por ejemplo, la expresin x+5*y se evala del siguiente modo: 1. Se calcula 5 * y, pues el operador * tiene mayor prioridad que el operador +. 2. Se suma a x el resultado de la operacin anterior. Sin embargo, si escribimos (x + 5) * y los parntesis modifican la prioridad, evalundose en primer lugar la expresin x + 5, y multiplicando este valor por y. Cuando en una expresin intervienen operadores con el mismo nivel de prioridad, se evalan en el sentido indicado en la primera columna de la tabla.
Operadores
! ~ ++ -- (cast) *4 &5 sizeof * / % + << >> < <= > >= == != & ^ | && ||
4 5
24
Introduccin al Lenguaje C
13 14 15
?: operadores de asignacin ,
Efecto lateral
Cuando en una expresin se emplea ms de una vez un mismo operando, y en alguna de ellas se modifica su valor, pueden producirse errores de efecto lateral. Por ejemplo, sea la secuencia i = 2; matriz[i] = x + (i = 4); En la segunda sentencia, el elemento matriz[i] se refiere el elemento i-simo de un vector llamado matriz. Sin embargo, en esa expresin no se asegura que el resultado se almacene en matriz[2] o en matriz[4]. Para evitar este tipo de problemas es necesario seguir las siguientes reglas: No utilizar operadores ++ o -- sobre variables que se empleen ms de una vez en una expresin. No utilizar operadores ++ o -- sobre variables que se empleen ms de una vez como argumento de una funcin.
#include
Indica al compilador que incluya un archivo fuente. El formato es #include "nom_fich" o bien, #include <nom_fich> siendo nom_fich un nombre vlido de fichero DOS. Puede decirse que cuando el compilador se encuentra una lnea #include, reemplaza dicha lnea por el fichero nom_fich. El uso de comillas dobles "" o ngulos < > afecta a dnde se buscar el fichero nom_fich. En el primer caso se busca en el siguiente orden:
1. Introduccin
25
1. Directorio en el que se est trabajando. 2. Directorios indicados al compilar el programa desde la lnea de rdenes del DOS. 3. Directorios indicados en la implementacin de Turbo C. En el segundo caso el orden de bsqueda es el siguiente: 1. Directorio definido con la opcin -I del compilador. 2. Directorio indicado en el Men Principal de Turbo C. 3. Directorios definidos en la implementacin de Turbo C. En ambos casos se supone que el nombre del fichero no va acompaado de ninguna trayectoria de directorio. En caso de que el nombre del fichero incluya una ruta de acceso, slo se buscar en ese directorio. As, en el caso #include "C:\MIDIR\MIFICH.H" o bien #include <C:\MIDIR\MIFICH.H> la bsqueda se realiza slo en el directorio C:\MIDIR, producindose un mensaje de error si no se encuentra el archivo. La directiva #include se usa principalmente para la inclusin de los denominados archivos de cabecera. La inclusin de estos archivos es necesaria cuando se utilizan las funciones de biblioteca. Estas funciones emplean tipos de datos y variables propias que se definen en esos archivos. Por ejemplo, el archivo de cabecera requerido por las funciones printf() y scanf() (junto con otras muchas ms) se llama stdio.h, y por ello siempre que un programa maneje esas funciones deber incluir una lnea como #include <stdio.h> De cualquier modo, puede utilizarse para incluir cualquier archivo fuente.
#define
En su forma ms simple se usa para asociar a un identificador una cadena de caracteres que lo sustituir cada vez que lo encuentre el compilador en el programa. Al identificador se le denomina macro y al proceso de sustituir el identificador por la cadena de caracteres se le llama sustitucin de macro. El formato general para esta directiva es #define Por ejemplo, en #define #define CIERTO FALSO 1 0 macro cadena
cuando el compilador encuentra el nombre de macro CIERTO lo sustituye por el valor 1. Cuando encuentra el nombre FALSO lo sustituye por el valor 0. Tambin es vlido #define MENSAJE ... ... printf (MENSAJE); que es equivalente a la sentencia "Error de E/S \n"
26
Introduccin al Lenguaje C
printf ("Error de E/S \n"); No se produce el proceso de sustitucin de macro en el caso printf ("MENSAJE"); que muestra en pantalla la cadena de caracteres MENSAJE. Tambin se pueden escribir sentencias como #define TECLA printf ("\nPulse cualquier tecla...") ... ... TECLA; Una vez definida una macro, puede utilizarse en la definicin de macros posteriores: #define #define #define A 1 B 2 TRES A + B
Si la definicin de la macro es demasiado larga, se indica mediante una barra invertida \ y se contina la definicin en la siguiente lnea. #define LARGO"Este mensaje es \ demasiado largo" Por convenio, los nombres de macros se escriben en mayscula.
Ejercicios
1. 2.
Representa de 3 maneras diferentes el carcter nueva lnea.
Indica el significado de cada uno de los siguientes elementos a) 12 e) '\N' b) 012 f) '\x12' c) 0x12 g) '\x01b' d) 0X12 h) '\034'
3.
Indica cules de los siguientes identificadores no son correctos y por qu. a) contador e) dias2 b) CONTADOR f) 2dias c) _hola g) Suma_Total d) hola_ h) Suma-Total
4.
Sean x, y, z, u, v y t, variables que contienen, respectivamente, los valores 2, 3, 4, 5, 6 y 7, qu almacenarn despus de ejecutar las siguientes sentencias? x++; y = ++z; t = - -v; v = x + (y *= 3) / 2; u = x + y / 2;
1. Introduccin
27
5. 6.
Indica cules de las expresiones siguientes son verdaderas y cules falsas, suponiendo que x, y, z y t, almacenan, respectivamente los valores 20, 10, 5 y 2. a) x > y && z > t d) !0 g) 0 != !1 j) x * 0 m) !(0 == 0) ) 1 && !0 || 1 b) e) h) k) n) o) x < y && z > t !1 0 == !1 x*y 10 > 5 && !(10 < 9) || 3 <= 4 1 && !(0 || 1) c) f) i) l) x < y || z > t 0 != 1 200 || x !!0
7.
Sea x una variable entera que almacena el valor 10 Qu almacenar y despus de las siguientes sentencias? a) y = (x > 9 ? ++x : -- x); b) y = (x > 9 ? x++ : x--); Y si x almacenase el valor 8?
8.
Una temperatura en grados centgrados C, puede ser convertida en su valor equivalente de la escala Fahrenheit de acuerdo a la siguiente frmula: F = (9 / 5) C + 32 Escribe un programa C que solicite una temperatura en grados centgrados por teclado y presente en pantalla la temperatura Fahrenheit equivalente. (ATENCIN a la divisin 9/5).
9.
Escribe un programa que lea un nmero hexadecimal entero del teclado (cdigo de formato %x), intercambie los bytes alto y bajo, y presente el resultado en hexadecimal en la pantalla. (NOTA: Declara la variable que almacenar el valor hexadecimal como unsigned int y utiliza los operadores << y >>).
10.
Escribe un programa que lea un nmero hexadecimal del teclado y manipule sus bits de acuerdo al siguiente criterio: Poner a 0 los bits 0, 3, 6 y 9. Poner a 1 los bits 2, 5, 8 y 11. Cambiar el estado del resto. Presenta en pantalla el resultado en hexadecimal. (NOTA: Declara la variable que almacenar el valor hexadecimal como unsigned int y utiliza los operadores de tratamiento de bits).
11.
Escribe un programa que lea dos nmeros enteros del teclado y presente en pantalla el triple de la diferencia entre el mayor y el menor. (Para saber cul es el mayor utiliza el operador ?:)
12.
Escribe un programa que capture por teclado un nmero entero de 4 cifras y lo redondee a la centena ms prxima, presentando el resultado en pantalla.
28
Introduccin al Lenguaje C
Error! Marcador no definido.TIPO PALABRA RESERVADA sin valor carcter entero coma flotante (simple precisin) coma flotante (doble precisin) void char int float double
TAMAO EN BYTES 0 1 2 4 8
Toda variable declarada de uno de los tipos anteriores (salvo void) se supone que puede ser positiva o negativa a menos que se aplique uno de los modificadores que veremos ms adelante. El tipo void se usa para definir funciones que no devuelven ningn valor (no tienen sentencia return). Por ejemplo, ninguna de las funciones main() utilizadas hasta ahora devuelve valores. Esto provoca un "Warning" en la compilacin, que se evita escribiendo void main (), en lugar de escribir simplemente main(). Adems, el tipo void se usa en ciertas operaciones de punteros que se estudiarn en el Captulo 7. Las variables de tipo char se usan para almacenar caracteres simples o nmeros enteros de tamao byte. Cuando se asigna un carcter simple a una variable de tipo char, se almacena su cdigo ASCII. Esto permite hacer operaciones matemticas con este tipo de variables. Por ejemplo, el siguiente programa Error! Marcador no definido.#include <stdio.h> void main () { char caracter; caracter = '$'; caracter *= 2; printf ("%c %d", caracter, caracter); }
muestra en pantalla H 72, puesto que la asignacin del carcter '$' a la variable caracter almacena en la variable su cdigo ASCII (36). El %c es el cdigo de formato para visualizar caracteres simples.
1. Introduccin
29
El tipo int se utiliza para declarar variables que almacenarn nmeros enteros, sin decimales. Los tipos float y double permiten almacenar nmeros con decimales.
Estas declaraciones deben situarse al principio de la funcin y antes de cualquier instruccin o llamada a funcin. Pueden ir precedidas por alguno de los modificadores de tipo que estudiaremos a continuacin.
Estos modificadores se aplican nicamente a los tipos char e int, salvo el long, que en un caso se puede aplicar al tipo double. La tabla siguiente muestra los rangos y tamaos de cada tipo, segn se apliquen los modificadores.
Tipo char
Rango
30
Introduccin al Lenguaje C
int
short
unsigned signed
0 a 65.535 -32.768 a 32.767 0 a 4.294.967.295 -2.147.483.648 a 2.147.483.647 3,4*10-38 a 3,4*10+38 1,7*10-308 a 1,7*10+308
long
unsigned signed
3,4*10-4932 a 1,1*10+4932
Respecto de estos modificadores hay que tener en cuenta los siguientes aspectos: a) El modificador signed no es necesario. Todas las variables enteras, por defecto, se consideran con signo. signed char signed int equivale a " char int
b) El tipo de dato char no es afectado por los modificadores short y long. short char equivale a long char ya char
c) Turbo C permite una notacin breve para declarar enteros sin usar la palabra reservada int. unsigned short " long " unsigned short unsigned long equivale a short int long int " " unsigned int
d) Los modificadores signed y unsigned provocan un error si se aplican a los tipos float y double. e) Los modificadores short y long no afectan a float. f) Al tipo double no le afecta el modificador short. El modificador long se puede aplicar a partir de la versin 2.00 de Turbo C. g) La precisin que se puede obtener para los tipos de datos reales viene dada por la siguiente tabla:
1. Introduccin
31
Una variable global es conocida por todo el programa, mientras que una variable local slo es conocida por la funcin o bloque en donde est definida.
Variables globales
Cuando una variable est declarada fuera de todas las funciones, incluso fuera de main (), se denomina GLOBAL y es conocida por todas las funciones del programa.
Variables locales
Una variable es LOCAL cuando se declara: dentro de una funcin como argumento de una funcin en un bloque de sentencias enmarcado por { } En cualquiera de estos casos, la variable slo es conocida por la funcin o bloque en donde est definida. Se crea al entrar a la funcin o bloque y se destruye al salir. Esto permite que dos variables locales a diferentes funciones pueden tener el mismo identificador, pero se consideran dos variables diferentes. Por claridad, no se recomienda utilizar nombres iguales para variables diferentes. A continuacin se muestra un programa que pretende ilustrar las diferencias entre variables locales y globales. En l se declara una variable global y, conocida por todas las partes del programa, y varias variables locales, todas con el mismo nombre x: una local a main(), otra local a un bloque dentro de main(), y otra local a una funcin.
Error! Marcador no definido.#include <stdio.h> int y; /* Global. Conocida tanto por main() como por MiFuncion() */ main () { int x; y = 100; x = 1; printf ("x=%d, y=%d", x, y) { int x; x = 2; printf ("x=%d, y=%d", x, y) MiFuncion () printf ("x=%d, y=%d", x, y)
/* Visualiza x=1, y=100 */ /* Comienza bloque */ /* Esta x es local al bloque */ /* Visualiza x=2, y=100 */ /* Visualiza x=3, y=100 */ /* Visualiza x=2, y=100 */
32
Introduccin al Lenguaje C
Clases de almacenamiento
Existen cuatro clases de almacenamiento, que se identifican con las palabras reservadas: auto extern static register
que anteceden al tipo de dato en la declaracin de las variables. El modo de almacenamiento de una variable determina qu partes del programa la conocen (mbito) qu tiempo permanece en la memoria (tiempo de vida)
/* Variable global */
1. Introduccin
33
/* Es la misma variable */
Esta redeclaracin es innecesaria cuando todas las funciones estn en el mismo mdulo. Sin embargo, si un programa consta de varios mdulos diferentes, es necesaria la palabra extern para que sea reconocida por mdulos distintos del que contiene la declaracin de la variable. Por ejemplo, supongamos que PROG1.C y PROG2.C son dos mdulos que se compilan separadamente y, posteriormente, se enlazan para formar un solo mdulo ejecutable.
FuncionExterna () main () { x = 10; y = 20; printf ("x=%d, y=%d", x, y); FuncionInterna (); FuncionExterna (); } } { printf ("x=%d, y=%d", x, y);
En el mdulo PROG1.C (que se compilar a PROG1.OBJ) hay una llamada a FuncionInterna(). Esta funcin no necesita la redeclaracin de x e y puesto que reside en el mismo mdulo en el que estas variables estn declaradas como globales. Sin embargo, hay una llamada a FuncionExterna(), y esta funcin reside en un mdulo distinto (PROG2.C) que se compilar por separado (a PROG2.OBJ). Para que FuncionExterna() pueda reconocer a las variables x e y que estn declaradas como globales en PROG1.C, es necesario redeclararlas como de clase extern. En caso contrario, PROG2.C "creera" que son variables locales propias no declaradas, por lo que se generara un error en la compilacin. A partir de los mdulos objeto PROG1.OBJ y PROG2.OBJ, mediante el enlazador, se crea un mdulo ejecutable, por ejemplo PROG.EXE. Este programa visualizara en 3 ocasiones la lnea x=10, y=20 puesto que para las tres funciones -main(), FuncionInterna() y FuncionExterna()- las variables x e y son las mismas.
34
Introduccin al Lenguaje C
En realidad no se puede saber cul es el valor de n pues, como veremos, n no se inicializa con ningn valor. Pero para este ejemplo podemos suponer que se inicializa a 0.
1. Introduccin
35
Inicializacin de variables
Al declarar una variable se le puede asignar un valor inicial, independientemente de que lo mantenga o no a lo largo de todo el programa. As, son vlidas sentencias como: unsigned int x = 40000; char letra = 'F'; register int b = 35; char cadena[12] = "Buenos das"; Las variables globales o estticas se inicializan a 0 si no se especifica ningn valor. Ambas deben inicializarse con expresiones constantes. Las variables estticas son inicializadas por el compilador una sola vez, al comienzo del programa. Las variables automticas y de registro tienen valores desconocidos hasta que se les asigne uno. Si tienen valores iniciales, se asignan cada vez que se ejecuta el bloque donde se definen.
36
Introduccin al Lenguaje C
Ejercicios
1.
Indica el tipo de dato ms adecuado para declarar variables que almacenen los valores cuyos rangos se muestran a continuacin: a) b) c) d) e) f) g) h) i) Nmeros enteros del 0 al 70.000 Nmeros enteros del 1 al 100 Nmeros reales del 1 al 100 Nmeros enteros del -100 al +100 Nmeros reales del -100 al +100 Nmeros enteros del 0 al 10.000 Cualquier letra mayscula Nmeros enteros del 0 al 5.000.000.000 Nmeros del -10.5 al 90.125
2.
Qu hace el siguiente programa? Error! Marcador no definido.#include <stdio.h> void main () { unsigned char caracter; scanf ("%c", &caracter); caracter > 127 ? caracter++ : caracter--; printf ("%c", caracter); }
3.
Indica el tipo de variable (local/global), mbito y tiempo de vida de las variables a, b, c y d de un programa que tenga la estructura que se muestra a continuacin:
int a; void main () { int b; ... } MiFuncion () { int c; static int d; ... }
1. Introduccin
37
E/S de caracteres
Funciones getche() y getch()
Son las dos funciones bsicas que capturan caracteres simples por teclado. Los programas que utilizan estas funciones deben incluir el archivo de cabecera conio.h mediante una sentencia #include. Ambas funciones devuelven el carcter ledo del teclado (sin esperar a pulsar la tecla ). La funcin getche() muestra en pantalla el carcter tecleado. No as la funcin getch(). El uso correcto de estas funciones es mediante sentencias del tipo char a; ... ... /* Tambin puede declararse como int */
38
Introduccin al Lenguaje C
a = getche ();
Estas funciones no tienen argumentos. No es obligatorio capturar en una variable el carcter ledo. As, es correcto escribir: printf ("\nPulse una tecla para continuar ..."); getch (); que detiene el programa hasta que haya un carcter disponible en el teclado.
Error! Marcador no definido.//Ejemplo: Visualiza el cdigo ASCII de un carcter tecleado #include <stdio.h> #include <conio.h> void main () { unsigned int caracter; printf ("\nPulse una tecla: "); caracter = getche (); printf ("\nSu cdigo ASCII es %u", caracter); }
Este programa produce un resultado casi idntico utilizando la funcin getch(). La nica diferencia es la no visualizacin del carcter que se teclea. El cdigo de formato %u se utiliza en printf() para visualizar nmeros declarados unsigned.
Funcin putchar()
Para la presentacin de caracteres en pantalla se puede usar, adems de printf() con el cdigo de formato %c, la funcin putchar(). Los programas que usen esta funcin deben incluir el archivo de cabecera stdio.h. El uso correcto de putchar() es char a; ... ... putchar (a); /* Tambin puede declararse como int */
Si la variable a es de tipo int se visualiza en pantalla el carcter almacenado en el byte menos significativo. Las sentencias
char letra; ... ... letra = 65; putchar (letra); provocan la visualizacin en pantalla del carcter A, cuyo cdigo ASCII es 65. Es equivalente a putchar ('A');
1. Introduccin
39
Tambin son posibles sentencias como putchar (getch ()); cuyo efecto es idntico al que produce la funcin getche().
Funcin gets()
La funcin bsica de entrada de cadenas caracteres por teclado es gets(). Esta funcin lee caracteres del teclado hasta pulsar la tecla , almacenndolos en la cadena indicada en el argumento y aadiendo el carcter nulo al final. char frase[31]; ... ... gets (frase); La funcin gets() no hace comprobacin del lmite. En la secuencia anterior nada impedir al usuario teclear ms de 30 caracteres. La consecuencia de ello es imprevisible, aunque con toda seguridad el programa no funcionar correctamente. Es importante tener en cuenta que no se permite la asignacin de cadenas de caracteres mediante el operador =. Este tipo de operaciones se realizan mediante funciones de la biblioteca estndar. Alguna de ellas se estudiarn e el Captulo 6. frase = gets ();
Funcin puts()
La funcin bsica de salida de cadenas de caracteres es puts(). Esta funcin escribe en pantalla la cadena de caracteres especificada en el argumento y provoca, adems, un salto de lnea. Es ms rpida que printf() pero no permite formatear la salida. Tanto gets() como puts() precisan la inclusin del fichero stdio.h.
40
Introduccin al Lenguaje C
#include <stdio.h> void main () { char frase[31]; puts ("\nTeclee una frase: "); gets (frase); printf ("\nUsted ha tecleado: %s", frase); }
En el ejemplo anterior el cdigo de formato %s se refiere a cadenas de caracteres. Un efecto similar al programa anterior lo produce la sentencia puts (gets (frase));
E/S formateada
La E/S con formato se realiza por medio de las funciones scanf() y prinft() que ya conocemos. Las estudiaremos ahora con ms detalle.
Funcin printf()
Como ya hemos visto, esta funcin precisa la inclusin del archivo de cabecera stdio.h y su formato general es: printf (cadena de control, lista de argumentos); La cadena de control determina como se presentarn el resto de argumentos mediante los caracteres de formato. Por cada carcter de formato de la cadena de control debe haber un argumento en la lista. En caso contrario el resultado no ser correcto. Los caracteres de formato vlidos vienen especificados en la tabla siguiente.
Carcter
CARCTER DE FORMATO %d %i %o %u %x %X %f %e %g %E %G %c %s
FORMATO DE SALIDA signed decimal int signed decimal int unsigned octal int unsigned decimal int unsigned hexadecimal int (con a, ..., f) unsigned hexadecimal int (con A, ..., F) [-]dddd.dddd [-]d.dddd o bien e[+/-]ddd el ms corto de %e y %f [-]d.dddd o bien E[+/-]ddd el ms corto de %E y %f carcter simple cadena de caracteres
1. Introduccin
41
Punteros
%% %n %p
Mediante los caracteres de formato pueden controlarse ciertos aspectos de la presentacin de los datos, como la longitud, el nmero de decimales y la justificacin a izquierda o derecha. Para indicar la longitud de un campo basta incluir un nmero entero entre el signo % y el carcter de formato. As, %5d indica que se va a presentar un nmero entero en un campo de 5 posiciones, justificando a la derecha (mientras no se indique lo contrario la justificacin es siempre a la derecha). As, printf ("\n[%5d]", 47); printf ("\n[%4d]", 47); printf ("\n[%3d]", 47); visualiza visualiza visualiza [47] [47] [47]
donde el smbolo representar el resto del captulo un espacio en blanco. Los corchetes se ponen slo para ver el efecto de los caracteres de formato. Si se desea justificacin a la izquierda, se indica con un signo menos. printf ("\n[%-5d]", 47); printf ("\n[%-4d]", 47); printf ("\n[%-3d]", 47); visualiza visualiza visualiza [47] [47] [47]
Cuando se presenta un valor numrico justificando a la derecha, puede hacerse que el campo se rellene con ceros en lugar de espacios en blanco, poniendo un 0 antes del indicador de tamao. As, la sentencia printf ("\n[%05d]", 47); visualiza [00047]
Este efecto no se produce si se solicita justificacin izquierda printf ("\n[%-05d]", 47); visualiza [47]
Cuando el dato sobrepasa el tamao del campo, se imprime completo printf ("\n[%3d]", 1234); visualiza [1234]
Si lo dicho anteriormente se aplica a cadenas de caracteres, el resultado es el que se indica a continuacin: printf ("\n[%5s]", "ABC"); printf ("\n[%05s]", "ABC"); printf ("\n[%-5s]", "ABC"); printf ("\n[%-05s]", "ABC"); printf ("\n[%5s]", "ABCDEF"); Aplicado a caracteres simples printf ("\n[%3c]", 'A'); printf ("\n[%-3c]", 'A'); visualiza visualiza [A] [A] visualiza [ABC] visualiza [ABC] visualiza [ABC] visualiza [ABC] visualiza [ABCDEF]
Si se quiere dar formato a un nmero con parte decimal se utiliza el cdigo de formato %m.nf
42
Introduccin al Lenguaje C
siendo m la longitud total del campo (incluyendo el punto) y n el nmero de decimales. As, el especificador de formato %7.2f define un campo de 7 posiciones del siguiente modo: 4 para la parte entera, 1 para el punto y 2 para los decimales. printf ("\n[%7.2f]", 12.3); printf ("\n[%07.2f]", 12.3); printf ("\n[%-7.2f]", 12.3); visualiza visualiza visualiza [12.30] [0012.30] [12.30]
Cuando el nmero de decimales es mayor que n, se redondea la ltima cifra printf ("\n[%7.2f]", 12.348); printf ("\n[%7.2f]", 12.342); visualiza visualiza [12.35] [12.34]
Si este formato se aplica a cadenas de caracteres o nmeros enteros, n especifica el tamao mximo del campo. As, %3.5s define un campo de presentacin para una cadena de al menos 3 caracteres y no ms de 5. Si la cadena sobrepasa el tamao mximo, se trunca. printf ("\n[%3.5s]", "ABC"); printf ("\n[%-3.5s]", "ABC"); printf ("\n[%3.5s]", "ABCDEFG"); printf ("\n[%3.5s]", "AB"); printf ("\n[%5.3s]", "ABCDE"); visualiza visualiza visualiza visualiza visualiza [ABC] [ABC] [ABCDE] [AB] [ABC]
Pueden utilizarse modificadores de formato para visualizar enteros short y long, poniendo, respectivamente, h y l antes de d, u, o, x, X. %hd %ld %hu %lu para visualizar variables short int para visualizar variables long int para visualizar variables unsigned short int para visualizar variables unsigned long int
El modificador l tambin se puede aplicar a e, f, g, E, G. %lf para visualizar variables long double
Funcin scanf()
Es la funcin de entrada de datos con formato de propsito general que hemos visto en el Captulo 1. La sintaxis es similar a la de printf(): scanf (cadena de control, lista de argumentos); aunque aqu la cadena de control no debe interpretarse igual que en printf(). Clasificaremos los caracteres que pueden aparecer en la cadena de control en 6 categoras: Especificadores de formato Caracteres blancos Caracteres no blancos Carcter * Modificadores de longitud Juego de inspeccin
Especificadores de formato: Son los mismos que en printf() salvo %g y %u y aadiendo %h. As, la sentencia
1. Introduccin
43
captura un carcter y lo almacena en la variable a. El operador & es necesario en scanf() para simular las llamadas por referencia7, y hace que la funcin trabaje internamente con la direccin de la variable. No es necesario cuando el dato a capturar es una cadena de caracteres. char cadena[80]; ... ... scanf ("%s", cadena); aunque tendra el mismo efecto la sentencia scanf ("%s", &cadena); La razn por la que no es necesario el operador & cuando se trata de cadenas, es que el nombre de una cadena (en general, de una matriz) sin ndices identifica la direccin del primer elemento. Caracteres blancos: Son separadores de datos de entrada. Se entiende como carcter blanco cualquiera de los siguientes: Espacio(s) en blanco Tabulador(es) Return, Intro o salto de lnea () Cuando se especifica un carcter blanco en la cadena de control, scanf() lee, pero no guarda, cualquier nmero de caracteres blancos hasta que encuentre el primero que no lo sea. La sentencia scanf ("%c %d %s", &a, &b, &c); almacena en a, b y c, respectivamente, datos introducidos de cualquiera de los siguientes modos: Y 19 Hola Y <TAB> 19 Hola Y 19 Hola
y cualquier combinacin similar. Hay que tener precauciones con el uso de los espacios en blanco. Una sentencia como scanf ("%s ", cadena); no volver hasta que no se teclee un carcter no blanco, puesto que el espacio que hay despus de %s hace que se lean y descarten espacios, tabuladores y saltos de lnea. Caracteres no blancos: Se toman como separadores de los datos de entrada. Por ejemplo, en la sentencia scanf ("%dHOLA%c", &num, &car);
En las llamadas por referencia a funciones, cualquier cambio operado sobre los parmetros dentro de la funcin tienen efecto sobre las variables utilizadas en la llamada. No ocurre as en las llamadas por valor en las que los cambios en los parmetros dentro de la funcin no se reflejan en las variables de la llamada. Estudiaremos esto con detalle en el Captulo 6, dedicado a Funciones.
44
Introduccin al Lenguaje C
se lee un entero y se almacena en num, los caracteres HOLA y se descartan (es necesario teclearlos), y un carcter cualquiera y se almacena en car. Debido a ello hay que poner especial cuidado en no usar la cadena de control de scanf() como en printf(), y NO ESCRIBIR sentencias como: scanf ("Teclee un nmero: %d", &num); Carcter *: Cuando se coloca un * entre el smbolo % y el carcter de formato, se lee el tipo de dato especificado pero no se asigna a ninguna variable. La sentencia scanf ("%d %*c %d", &num1, &num2); lee un entero y lo almacena en num1, lee un carcter y lo descarta (no se asigna a ninguna variable, y lee otro entero y lo guarda en num2. De este modo, si a una sentencia como la anterior se responde tecleando algo como 10-20 se almacenara el valor 10 en num1 y el valor 20 en num2. Modificadores de longitud: Es posible limitar el nmero de caracteres que se admiten en un campo escribiendo un nmero despus del smbolo % del carcter de formato. As, la sentencia scanf ("%5s", frase); captura cualquier cadena de caracteres tecleada, pero slo almacena en frase los 5 primeros. Sin embargo el resto de caracteres no se descartan, sino que quedan en el bffer del teclado disponibles para ser ledos por otra sentencia scanf(). El siguiente programa ilustra el uso de scanf() con modificadores de longitud.
Error! Marcador no definido.//Ejemplo de scanf() con modificador de longitud #include <stdio.h> void main () { long int n1, n2; printf ("\nTeclee 2 nmeros enteros: "); scanf ("%5ld %5ld", &n1, &n2); printf ("\nLos nmeros tecleados son %ld y %ld", n1, n2); } Si en este programa a la sentencia scanf() se responde con 123456 se muestra en pantalla Los nmeros tecleados son 12345 y 6 Juego de inspeccin: Se utiliza slo con %s y permite especificar qu caracteres se aceptarn. Se indican entre corchetes. Por ejemplo char frase[30]; ... ... scanf ("%[abc]s", frase);
1. Introduccin
45
admite slo los caracteres a, b, c. Cuando se teclea un carcter que no est en el juego de inspeccin finaliza la entrada para esa variable. Sin embargo, la sentencia continuar admitiendo caracteres hasta que se teclee , dejando los caracteres no capturados en el bffer del teclado, disponibles para otra sentencia scanf() posterior. Por ello, si se tienen dos sentencias como scanf ("%[abc]s", frase); scanf ("%s", cadena); y se teclea, por ejemplo abcbbapabcfgts en frase se almacenar la cadena abcbba, mientras que en cadena se almacenar abcfgts. El carcter p, primero diferente de [abc], acta como finalizador de la entrada para la variable frase. Pueden especificarse rangos. Por ejemplo scanf ("%[a-z]s", frase); admite cualquier carcter comprendido entre la a y la z. Tambin, la sentencia scanf ("%[0-9][p-t]s", frase); admite cualquier dgito numrico y los caracteres pqrst. Es posible definir juegos inversos de inspeccin mediante el carcter ^. ste le indica a scanf() que debe aceptar cualquier carcter que no est en el juego de inspeccin. Por ello, la sentencia scanf ("%[^0-9]s", frase); finaliza la entrada al teclear un dgito numrico.
La funcin fprintf()
Esta funcin es prcticamente idntica a printf(). Pero fprintf() permite asociar la salida a diferentes dispositivos (pantalla, impresora, archivo de texto u otro). La sintaxis correcta para esta funcin es fprintf (dispositivo, cadena de control, lista de argumentos); es decir, incluye un primer argumento que relaciona la salida con un dispositivo. En realidad, ese primer argumento debe ser un puntero a una estructura predefinida en C llamada FILE, de la que hablaremos en el Captulo 10. De cualquier modo, para nuestros propsitos actuales, nos basta con identificarlo de algn modo con un dispositivo. Como veremos en su momento, C asocia los dispositivos a unas zonas o bffers de memoria denominados canales. Cuando un programa empieza a ejecutarse se abren automticamente 5 canales, entre ellos el asociado a la impresora. A este dispositivo se le referencia mediante la palabra stdprn. Por lo tanto, fprintf() direcciona su salida a la impresora cuando se escribe de la forma fprintf (stdprn, cadena de control, lista de argumentos); Todas las dems caractersticas de fprintf() son idnticas a las de printf().
46
Introduccin al Lenguaje C
Funciones de biblioteca
Funcin clrscr(): Borra la pantalla y sita el cursor en la esquina superior izquierda. No necesita argumentos ni devuelve ningn valor. Funcin clreol(): Borra una lnea de la pantalla desde la posicin del cursor hasta el final de dicha lnea. Tampoco necesita argumentos ni devuelve valor alguno. Funciones insline() y delline(): La funcin insline() inserta una lnea en blanco debajo de la del cursor, desplazando el resto una lnea hacia abajo. Anlogamente, la funcin delline() elimina la lnea en donde est el cursor, desplazando las de debajo hacia arriba. Funciones gotoxy(), wherex() y wherey(): La funcin gotoxy() se encarga del posicionamiento del cursor. Se escribe int fila, columna; ... ... gotoxy (columna, fila); Las funciones wherex() y wherey() devuelven las coordenadas de la posicin actual del cursor: int fila, columna; ... ... columna = wherex (); fila = wherey (); En estas 3 funciones, las variables fila y columna deben declararse de tipo int. Funcin movetext(): Copia texto de una zona de la pantalla a otra. La sintaxis de esta funcin es int x1, y1, x2, y2, x3, y3; ... ... movetext (y1, x1, y2, x2, y3, x3); siendo: x1, y1: columna, fila de la esquina superior izquierda de la zona de la pantalla a desplazar. x2, y2: dem de la esquina inferior derecha. x3, y3: columna, fila de la esquina superior izquierda de la pantalla a donde se desplaza el texto.
1. Introduccin
47
La funcin no borra el texto de la posicin original. Devuelve 0 si alguna coordenada est fuera de rango, y 1 en caso contrario. Funciones highvideo(), lowvideo() y normvideo(): Las dos primeras ponen, respectivamente, el texto de la pantalla en alta y baja intensidad. La funcin normvideo() restaura la intensidad de la pantalla al estado en que se encontraba al iniciarse el programa. Funciones textcolor(), textbackground() y textattr(): La funcin textcolor() establece el color del texto que se presenta a partir del momento en que se ejecuta la funcin. textcolor (color_de_carcter); El parmetro color_de_carcter es un nmero comprendido entre 0 y 15. Cada uno de estos nmeros tiene asociado un color. Adems, en el archivo conio.h se define una macro para cada color. Esto queda reflejado en la tabla siguiente:
VALOR 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 128
COLOR Negro Azul Verde Turquesa Rojo Morado Marrn Blanco Gris Azul intenso Verde intenso Turquesa intenso Rojo intenso Morado intenso Amarillo Blanco intenso Parpadeo
MACRO BLACK BLUE GREEN CYAN RED MAGENTA BROWN LIGHTGRAY DARKGRAY LIGHTBLUE LIGHTGREEN LIGHTCYAN LIGHTRED LIGHTMAGENTA YELLOW WHITE BLINK
Teniendo esto en cuenta, para conseguir que un texto se presente, por ejemplo, en color verde, previamente a su escritura debe hacerse textcolor (2); o bien textcolor (GREEN); Sin embargo, esto no tiene efecto si se utilizan las funciones printf(), puts(), gets() y putchar(). En su lugar, deben utilizarse las funciones cprintf(), cputs(), cgets() y putch(), respectivamente. Sigue siendo vlida la funcin getche(). Para conseguir el parpadeo del carcter debe hacerse una operacin OR entre el color y el valor 128 (BLINK). Por lo tanto, si queremos presentar un texto en amarillo parpadeante debemos escribir textcolor (YELLOW | BLINK);
48
Introduccin al Lenguaje C
La funcin textbackground() establece el color de fondo para todo texto que se escriba en pantalla a continuacin. textbackground (color_de_fondo); siendo color_de_fondo un valor comprendido entre 0 y 7, correspondiente a la tabla anterior. La funcin textattr() establece el byte de atributo completo (color de carcter, color de fondo, parpadeo si/no e intensidad alta/baja). Se escribe textattr (atributo); donde atributo es un byte cuyo significado se muestra a continuacin
7 P
6 F
5 F
4 F
3 I
2 C
1 C
0 C
donde CCC son los bits que codifican el color del carcter, I es el bit de intensidad, FFF indican color de fondo, y P es el bit de parpadeo. La forma ms cmoda de usar esta funcin es la siguiente: se multiplica el nmero del color de fondo de la tabla por 16 y se hace una operacin OR con el color de carcter. Si adems se quiere que el texto parpadee, debe hacerse una operacin OR con el valor 128 (BLINK). As, para obtener un texto en amarillo parpadeando sobre fondo azul, debe hacerse: textattr (YELLOW | BLINK | BLUE * 16); Funcin textmode(): Asigna un determinado modo de vdeo a la pantalla de texto: textmode (modo_de_vdeo); Los valores permitidos para modo_de_vdeo y las macros asociadas se muestran en la tabla siguiente.
MODO DE VDEO 0 1 2 3 7 -1
DESCRIPCIN Blanco y negro, 40 columnas Color, 40 columnas Blanco y negro, 80 columnas Color, 80 columnas Monocromo Modo anterior
1. Introduccin
49
Coloca el cursor en la posicin de la pantalla sealada con #;# (el primer nmero indica la fila y el segundo la columna) ESC[#A Mueve el cursor # lneas hacia arriba ESC[#B Mueve el cursor # lneas hacia abajo ESC[#C Mueve el cursor # columnas hacia la derecha ESC[#D Mueve el cursor # columnas hacia la izquierda ESC[s Guarda la posicin actual del cursor ESC[u Recupera la posicin del cursor previamente guardada con la secuencia anterior Borrado ESC[2J Borra la pantalla y coloca el cursor en la esquina superior izquierda de la pantalla ESC[K Borra todos los caracteres desde la posicin actual del cursor hasta el final de la lnea Establecer modo ESC[#;...;#m Llama a las funciones grficas especificadas de grficos mediante los dgitos #. Estas funciones permanecen activas hasta la siguiente aparicin de esta secuencia de escape. Los valores permitidos para # se muestran en la tabla de la pgina siguiente Establecer modo ESC[=#h Cambia el ancho o el tipo de pantalla. Los de vdeo valores permitidos para # se muestran en la tabla de la pgina siguiente ESC[=#l Restablece el modo utilizando los mismos valores que utiliza la secuencia anterior, salvo el modo 7 que desactiva el ajuste automtico de lnea. El ltimo carcter de la secuencia es la L minscula Reasignacin de ESC[#;cad;#p Permite cambiar la definicin de las teclas a cadenas para el una cadena especfica. El smbolo # teclado representa el cdigo o cdigos generados por la tecla a reasignar. cad es el cdigo ASCII correspondiente a un solo carcter o a una cadena entre comillas. Ejemplo 1: ESC[65;66p convierte la A en B. Ejemplo 2: ESC[0;59;"CLS";13p Asigna a la tecla F1 (que genera los cdigos 0 y 59) la cadena CLS junto con (cdigo 13). Valores permitidos ATRIBUTOS 0 Desactiva todos los atributos para # en la DE TEXTO 1 Alta intensidad activada secuencia de escape 4 Subrayado (monitor monocromo) ESC[#;...;#m 5 Parpadeo activado 7 Vdeo inverso activado 8 Oculto activado COLOR DE 30 Negro CARCTER 31 Rojo 32 Verde
DESCRIPCIN
50
Introduccin al Lenguaje C
COLOR DE FONDO
33 34 35 36 37 40 41 42 43 44 45 46 47 0 1 2 3 4 5 6 7 13 14 15 16 17 18 19
Amarillo Azul Magenta Violeta Blanco Negro Rojo Verde Amarillo Azul Magenta Violeta Blanco 40x25 monocromo (texto) 40x25 color (texto) 80x25 monocromo (texto) 80x25 color (texto) 320x200 color (grficos) 320x200 monocromo (grficos) 640x200 monocromo (grficos) Activa ajuste automtico de lnea 320x200 color (grficos) 640x200 color (grficos 16 colores) 640x350 mono (grficos 2 colores) 640x350 color (grficos 16 colores) 640x480 mono (grficos 2 colores) 640x480 color (grficos 16 colores) 320x200 color (grficos 256 colores)
Una secuencia de escape ANSI se enva, por ejemplo, mediante la funcin printf(). Con ello no se visualizan los caracteres de la secuencia de escape, sino que se consigue el efecto descrito para cada una de ellas. Pero para que estas secuencias tengan el efecto esperado es necesario instalar el controlador ANSI.SYS en el archivo CONFIG.SYS mediante una sentencia DEVICE o DEVICEHIGH. La sintaxis de la instalacin de este controlador es: DEVICE = [unidad:] [ruta] ANSI.SYS de modo que si el archivo ANSI.SYS est en el directorio \DOS de la unidad C:, en el fichero CONFIG.SYS debe haber una lnea device = c:\dos\ansi.sys o, si se desea instalar en memoria superior devicehigh = c:\dos\ansi.sys Veamos algunos ejemplos que ilustran el uso de estas secuencias de escape. Posicionamiento del cursor: La secuencia de escape para posicionar el cursor es ESC [f;cH (f = fila, c = columna) Para situar el cursor en la fila 5, columna 20, se hace printf ("\033[5;20H"); y produce exactamente el mismo efecto que
1. Introduccin
51
gotoxy (20, 5); No obstante para este tipo de operaciones es ms conveniente definir una funcin que reciba en los parmetros las coordenadas: pon_cursor (int fila, int columna) { printf ("\033[%d;%dH", fila, columna); } En un programa se llamara a esta funcin, por ejemplo, mediante pon_cursor (5, 20); Borrado de pantalla: La secuencia ANSI que borra la pantalla es ESC [2J Por tanto, la sentencia printf ("\033[2J"); produce el mismo efecto que la funcin de biblioteca clrscr(). Se puede abreviar la escritura de esta secuencia mediante una macro como #define CLS printf ("\033[2J")
y utilizar en el programa sentencias CLS; Redefinicin de teclas: Mostraremos el uso de esta secuencia mediante un ejemplo que asigna a la tecla F1 (que al ser pulsada genera los cdigos 0 y 59) la cadena de caracteres DIR C:/P . Esto se consigue con la secuencia ESC [0;59;"DIR C:/P";13p Con una funcin printf() esto se escribe printf ("\033[0;59;\"DIR C:/P\";13p"); o tambin printf ("\033[0;59;%c%s%c;13p", 34, "DIR C:/P", 34); donde hay que tener en cuenta que el cdigo ASCII del carcter comilla doble (") es 34.
Redireccionamiento de la E/S
Las operaciones de E/S realizadas por funciones referidas a la consola (printf(), scanf(), ...) pueden redirigirse a otros dispositivos mediante los operadores > redireccin de la salida < redireccin de la entrada
52
Introduccin al Lenguaje C
Redireccin de la salida
Sea el programa de la pgina 2, compilado y linkado como DOCENA.EXE. Si desde el indicador del DOS escribimos: C:\> docena >prn la frase "Una docena son 12 unidades" no se muestra en pantalla, sino que se dirige al dispositivo prn (impresora). Se puede tambin redireccionar la frase a un fichero de texto llamado, por ejemplo, FRASE.TXT, mediante C:\> edad >frase.txt Esta orden crea en el directorio raz un fichero de texto llamado FRASE.TXT que almacena la frase "Una docena son 12 unidades". Si el fichero ya existe, se pierde su contenido anterior. Cada vez que se redirecciona la salida a un fichero, ste se crea de nuevo. As, la secuencia C:\> prog1 >fich.txt C:\> prog2 >fich.txt crea un archivo FICH.TXT que almacena la salida nicamente de PROG2. Si se desea aadir la salida de un programa a un fichero existente sin destruir su contenido, se utiliza el operador >>. La secuencia C:\> prog1 >fich.txt C:\> prog2 >>fich.txt almacena en FICH.TXT la salida de ambos programas PROG1 y PROG2.
Redireccin de la entrada
Sea ahora el programa PIES.C de la pgina 5 compilado y linkado como PIES.EXE. Creemos un fichero de texto llamado DATO.DAT que almacene el nmero 3. Esto se consigue desde el indicador del DOS mediante la secuencia C:\> copy con dato.dat 3 ^Z C:\> Si escribimos C:\> pies <dato.dat la sentencia scanf() del programa ya no lee el dato del teclado. Al estar la entrada redireccionada, lo toma del archivo DATO.DAT. Veramos entonces en pantalla los mensajes generados por printf(). Algo como Pies?: 3 pies = 0.925200 metros
1. Introduccin
53
C:\> pies <dato.dat >mensajes.txt no veremos en pantalla nada, y se crea un fichero de texto en el directorio raz llamado MENSAJES.TXT que almacena los mensajes generados por las dos sentencias printf().
54
Introduccin al Lenguaje C
Ejercicios
1.
Escribe un programa que lea del teclado un cdigo ASCII (entero comprendido entre 0 y 255) y presente en pantalla el carcter correspondiente.
2.
Escribe un programa que lea del teclado un carcter cualquiera y presenta en pantalla su cdigo ASCII en decimal, octal y hexadecimal.
3.
Escribe un programa que lea del teclado dos nmeros en coma flotante cuya parte entera se asume que no supera 3 dgitos, y muestre en pantalla su suma ajustada a la derecha. Por ejemplo, si los nmeros son 23.6 y 187.54 el programa debe mostrar: 23,60 187,54 ---------211,14
4.
Escribe un programa que lea 2 nmeros enteros de 3 dgitos e imprima su producto. Por ejemplo, si los nmeros son 325 y 426 se presentar en el formato 325 x 426 -----------1950 650 1300 -----------138450
5.
La fecha de Pascua corresponde al primer Domingo despus de la primera Luna llena que sigue al equinoccio de Primavera, y se calcula con las siguientes expresiones: A B C D E N = = = = = = resto de (ao / 19) resto de (ao / 4) resto de (ao / 7) resto de (19 * A + 24) / 30 resto de (2 * B + 4 * C + 6 * D + 5) / 7 22 + D + E
en el que N indica el nmero del da del mes de Marzo (Abril si N > 31, en cuyo caso el da es N - 31) correspondiente al Domingo de Pascua. Escribe un programa que acepte un ao por teclado y muestre en pantalla la fecha del primer Domingo de Pascua con el formato dd/mm/aaaa.
6.
8
Escribe un programa que lea del teclado los siguientes datos de 2 personas: nombre, edad, altura y peso. El programa enviar a impresora un listado con el formato:
Utilizar en todas las salidas a pantalla funciones de control de la pantalla de texto o secuencias de escape ANSI (borrado de pantalla, posicionamiento del cursor, colores, etc.)
1. Introduccin
55
EDAD xx xx xx,xx
56
Introduccin al Lenguaje C
1. Introduccin
57
else puts ("2"); el else est asociado a if (y). C siempre asocia los else al if ms cercano que no tenga ya un else. Para que en la sentencia anterior el else se asocie a if (x), hay que colocar adecuadamente las llaves {}. if (x) { if (y) puts ("1"); } else puts ("2"); En C tambin se dispone de la estructura if (condicin1) sentencia1; else if (condicin2) sentencia2; else if (condicin3) sentencia3; ... else sentenciaN; que va evaluando, sucesivamente, condicin1, condicin2, ... Cuando encuentra una cierta, se ejecuta la sentencia correspondiente y se finaliza el if. Si ninguna es cierta se ejecuta la sentencia que acompaa al else (que no es obligatorio). Como siempre, sentencia1, sentencia2, ..., pueden ser una sola sentencia o un grupo de ellas, en cuyo caso se encierran entre llaves {}. El siguiente programa usa esta estructura para determinar si un nmero es positivo, negativo o cero. Error! Marcador no definido.#include <stdio.h> #include <conio.h> void main () { int n; clrscr (); printf ("Teclee un nmero entero: "); scanf ("%d", &n); if (n > 0) puts ("Positivo"); else if (n < 0) puts ("Negaivo"); else puts ("Cero"); }
La estructura switch
La estructura switch inspecciona una variable y la va comparando con una lista de constantes. Cuando encuentra una coincidencia, se ejecuta la sentencia o grupo de sentencias asociado. La forma de switch es switch (variable) { case cte1: sentencia; break; case cte2: sentencia; break; ... ... default: sentencia; }
58
Introduccin al Lenguaje C
donde variable es una variable o cualquier expresin que devuelva un valor. La sentencia switch compara la variable con cte1, cte2, ..., y si encuentra una coincidencia, ejecuta la sentencia correspondiente. Por sentencia debemos entender tanto una sentencia simple como un grupo de sentencias (que, en este caso, no se encierran entre llaves). Si no se encuentra ninguna coincidencia se ejecuta la seccin default (que no es obligatoria). El siguiente segmento de programa muestra cmo se utiliza la sentencia switch. char c; ... ... c = getche (); switch (c) { case 'a': funcion_a (); break; case 'b': funcion_b (); break; default: puts ("No se ha pulsado ni a ni b"); } Fijmonos que la sentencia switch busca coincidencias exactas, por lo que no es una alternativa a programas como el de la pgina anterior, ya que NO EST PERMITIDO imponer condiciones de desigualdad. No es correcto, por tanto int n; ... ... switch (n) { case > 0: puts ("Positivo"); break; case < 0: puts ("Negativo"); break; default: puts ("Cero"); } La sentencia break es opcional. Cuando se encuentra, provoca la salida de switch. En caso contrario continua la siguiente secuencia case o default aunque no se cumpla la condicin. Para aclarar esto, tomemos el siguiente ejemplo:
int c; ... ... scanf ("%d", &c); switch (c) { case 1: case 2: Funcion2 (); case 3: Funcion3 (); break; case 4: Funcion4_1 (); Funcion4_2 (); break; case 5: Funcion_5 (); default: FuncionX (); }
1. Introduccin
59
Se ejecuta Funcion2() y Funcion3() Funcion2() y Funcion3() Funcion3() Funcion4_1() y Funcion4_2() Funcion5() y FuncionX() FuncionX()
La sentencia default es opcional. Cuando no est no se ejecuta ninguna accin al fallar todas las coincidencias. Simplemente se abandona el switch sin hacer nada. Si hay sentencia default, el bloque de sentencias asociado se ejecuta cuando fallan todas las comparaciones o no hay un break anterior que lo impida. Las sentencias switch pueden anidarse, con lo que se permiten estructuras del tipo: switch (m) { case 1: Funcion1 (); break; case 2: switch (n) { case 21:
default:
Bucles
Existen en C tres tipos de bucles: for, while y do/while.
Bucles for
El bucle for es muy potente y flexible. Adems de permitir las mismas operaciones que cualquier for de otros lenguajes, tiene caractersticas que lo diferencian claramente de ellos. En su formato tradicional este bucle tiene la forma for (inicializacin; condicin; incremento) cuerpo_del_bucle; Vemos que for tiene tres secciones: inicializacin, en dnde se da un valor inicial a una variable de control del bucle; condicin, que es una expresin que devuelve un valor verdadero o
60
Introduccin al Lenguaje C
falso, y hace que el bucle se repita mientras sea cierta; e incremento, en dnde se determina la cuanta del incremento o decremento de la variable de control. Las tres secciones estn separadas por punto y coma. El cuerpo del bucle puede estar formado por una o por varias sentencias. En este ltimo caso deben encerrarse entre llaves {}. El flujo de sentencias en este bucle es el siguiente:
inicializacin
FALSA condicin
VERDADERA cuerpo_del_bucle
incremento
Fijmonos que el for se sigue ejecutando MIENTRAS la condicin sea verdadera. Veamos un par de ejemplos. En la siguiente secuencia se muestran en pantalla los nmeros del 1 al 10 y sus cuadrados. register int i; ... ... for (i = 1; i <= 10; i++) { printf ("\nValor de i: %d", i); 2 printf ("\nValor de i : %d", i * i); } En esta, se muestran en pantalla las letras maysculas de la A a la Z. char letra; ... ... for (letra = 'A'; letra <= 'Z'; letra++) printf ("\n%c", letra); Puede ponerse un incremento/decremento diferente de 1. El siguiente ejemplo muestra en pantalla los nmeros pares comprendidos entre 1 y 100, descendentemente: register int i; ... ... for (i = 100; i >= 1; i = i - 2) printf ("\n%d", i);
1. Introduccin
61
Estudiaremos ahora algunas formas de for que se apartan del uso tradicional. Es posible tener ms de una variable de control del bucle: En el bucle for las secciones de inicializacin e incremento pueden tener, a su vez, subsecciones, en cuyo caso van separadas por el operador secuencial (,). Un ejemplo es register int i, j; ... ... for (i = 0, j = 1; i + j < N; i++, j++) printf ("\n%d", i + j); que visualiza los N primeros nmeros impares. No debe confundirse esta sentencia con un anidamiento de bucles for. Un anidamiento tiene el siguiente aspecto: register int i, j; ... ... for (i = 0; i <= 100; i++) { ... for (j = 0; j <= 100; j++) { cuerpo_del_bucle; } ... } La condicin de salida del bucle no tiene por qu referirse a la variable de control: Esto queda ilustrado en el siguiente ejemplo: char a; register int i; ... ... for (i = 1; a != 's'; i++) { printf ("\n%d", i); a = getch (); } En este ejemplo se van mostrando en pantalla los nmeros 1, 2, ... hasta que se teclee el carcter s. El bucle for puede no tener cuerpo: Esta caracterstica permite crear retardos en un programa. El bucle register int i; ... ... for (i = 1; i <= 100; i++); provoca un retardo de 100 ciclos. El bucle for puede tener vaca cualquier seccin: En un bucle for puede faltar una, dos o las tres secciones. Por ejemplo, es correcto escribir register int i; ... ... for (i = 0; i != 10; ) { scanf ("%d", &i); printf ("\n%d", i); }
62
Introduccin al Lenguaje C
que va mostrando en pantalla los valores que se tecleen, finalizando al teclear el nmero 10 (que tambin se visualiza). Tambin es correcto un bucle como for ( ; ; ) { cuerpo_del_bucle; } que es un bucle sin fin. Estudiaremos ms adelante, en este mismo captulo, cmo abandonar un bucle infinito. Cualquier expresin vlida en C puede estar en cualquier seccin de un bucle for: La forma del bucle for no tiene que ajustarse necesariamente a la mostrada en la pgina 67. En realidad la forma correcta es: for (expresin1; expresin2; expresin3) cuerpo del bucle; siendo expresinN cualquier expresin vlida C. Podemos decir que, en general, el flujo de sentencias de un bucle for es:
expresin1
FALSA expresin2
VERDADERA cuerpo_del_bucle
expresin3
Error! Marcador no definido.#include <stdio.h> void main () { int t; for (mensaje (); t = lee_numero (); cuadrado (t)); } mensaje () { printf ("\nTeclee un nmero (0 finaliza): "); }
1. Introduccin
63
lee_numero () { int n; scanf ("%d", &n); return n; } cuadrado (int x) { printf ("\nEl cuadrado es %d", x * x); } Vamos a fijarnos en el bucle for de la funcin main() y explicarlo mediante el diagrama de flujo de la pgina anterior. 1. Se ejecuta la funcin mensaje() que muestra la cadena "Teclee un nmero (0 finaliza): ". (IMPORTANTE: Esta funcin slo se ejecuta esta vez). 2. Se evala la expresin t = lee_numero(), es decir, lee_numero() captura un nmero entero del teclado y lo devuelve almacenndolo en t. 2.1 Si la expresin t = lee_numero () devuelve FALSO, es decir, si se ha tecleado 0, finaliza el bucle. 2.2 En caso contrario contina el bucle. 3. Se ejecuta el cuerpo del bucle. En este caso, dado que for finaliza con punto y coma, no hay cuerpo del bucle. 4. Se ejecuta la funcin cuadrado(t) que visualiza el cuadrado del nmero tecleado. 5. Se vuelve al paso 2.
El bucle while
Tiene la forma while (expresin) cuerpo_del_bucle; siendo expresin cualquier expresin C vlida. El cuerpo_del_bucle, puede estar formado por una sentencia sencilla o por un bloque de sentencias, en cuyo caso, se encierran entre llaves {}. El flujo de sentencias es
FALSA expresin
VERDADERA cuerpo_del_bucle
64
Introduccin al Lenguaje C
Por lo tanto, en el bucle while el cuerpo_del_bucle se repite mientras expresin se evale como cierta. Veamos algunos ejemplos. char c; ... ... while (c != 's' && c != 'n') c = getche (); En esta sentencia se solicita un carcter del teclado mientras no se teclee el carcter n ni el carcter s. Cuando se teclea alguno de estos caracteres, se almacena en c y se abandona el bucle. El siguiente ejemplo es un caso de bucle while sin cuerpo. while (getch () != 13); El programa est detenido en esta sentencia hasta que se teclee (cdigo ASCII 13). El siguiente programa utiliza un bucle while para solicitar del usuario que adivine un nmero.
Error! Marcador no definido.#include <stdio.h> #include <stdlib.h> void main () { int num; int n = 0; randomize (); num = random (20) + 1; // Las funciones randomize() y random() permiten // generar nmeros aleatorios
while (n != num) { printf ("\nTeclee un nmero entre 1 y 20: "); scanf ("%d", &n); if (n == num) puts ("ACERTASTE); else if (n < num) puts ("TU NMERO ES MENOR"); else puts ("TU NMERO ES MAYOR"); } }
Respecto del bucle while es conveniente tener presente lo siguiente: El cuerpo del bucle no se ejecutar NUNCA si la primera vez no se cumple la condicin. El bucle puede ser INFINITO si no se modifican adecuadamente las variables de la condicin dentro del bucle.
El bucle do/while
Tiene la forma do cuerpo_del_bucle; while (expresin);
1. Introduccin
65
siendo sentencia una sentencia simple o un grupo de sentencias encerradas entre llaves {}, y expresin cualquier expresin vlida C. El flujo de ejecucin es el siguiente:
cuerpo_del_bucle
VERDADERA expresin
FALSA
Por lo tanto, en un bucle do/while el cuerpo_del_bucle se ejecuta al menos una vez, incluso aunque expresin se evale como falsa, puesto que la evaluacin se hace al final, justo lo contrario del bucle while, en el que la evaluacin de expresin se hace al principio. En el siguiente ejemplo se solicita un carcter del teclado hasta que se pulse cualquiera de los caracteres 'S' o 'N'.
#include <stdio.h> void main () { char tecla; do { printf ("\nPulse S o N: "); tecla = getch (); } while (tecla != 'S' && tecla != 'N'); }
Sentencia break
Es la misma sentencia que hemos visto para finalizar los case de la sentencia switch. Pero adems permite forzar la salida inmediata de un bucle (for, while o do/while) en cualquier momento, ignorando el resto de sentencias. Veamos un ejemplo:
#include <stdio.h> void main () { int n; for ( ; ; ) { printf ("\nTeclee un nmero: "); scanf ("%d", &n);
66
Introduccin al Lenguaje C
En este ejemplo, el bucle for se ejecutara sin fin a no ser por la sentencia if (!n) break; //Es lo mismo que if (n == 0) break;
Se van solicitando nmeros por teclado y visualizando sus cuadrados hasta que se teclee un 0 (!n se evaluara como cierto), en cuyo caso se ejecuta la sentencia break, que provoca la salida inmediata del bucle sin que se ejecute la sentencia printf del final.
Sentencia continue
Esta sentencia se utiliza en los bucles for, while y do/while. Cuando se ejecuta fuerza un nuevo ciclo del bucle, saltndose cualquier sentencia posterior. Por ejemplo, el bucle int i, n; ... ... for (i = 1; i <= 100; i++) { n = i / 2; if (i == 2 * n) continue; printf ("\n%d", i); } muestra en pantalla slo los nmeros impares, puesto que para los nmeros pares la expresin i == 2 * n se evala como cierta, ejecutndose la sentencia continue que fuerza de inmediato un nuevo ciclo del bucle. El siguiente programa muestra como acta la sentencia continue en un bucle do/while.
Error! Marcador no definido.#include <stdio.h> #include <conio.h> void main () { int n; int positivos = 0; clrscr (); do { printf ("\nTeclea un nmero (-99 finaliza): "); scanf ("%d", &n); if (n <= 0) continue; positivos++; } while (n != -99); printf ("\nHas tecleado %d nmeros positivos", positivos); }
1. Introduccin
67
La sentencia positivos++ slo se ejecuta cuando n es un nmero positivo. Si n es negativo o vale 0, se ejecuta continue que fuerza una nueva evaluacin de la condicin de salida del bucle.
etiqueta:
La etiqueta puede estar antes o despus del goto, pero siempre en la misma funcin. Realmente, en lenguajes con suficientes estructuras de control (como C) no suelen presentarse situaciones que hagan necesaria la sentencia goto. Sin embargo, en alguna ocasin puede ser conveniente, bien porque la velocidad de proceso es importante (un salto con goto es ms rpido que otro tipo de controles de bifurcacin), o bien porque su uso clarifica el cdigo. El caso ms habitual es la salida de varios niveles de anidamiento. for (...) { while (...) { for (...) { ... ... if (...) goto salir; ... ... } } } salir: ... ... En este ejemplo, la nica alternativa a goto sera la realizacin de varias comprobaciones en cada bucle que forzase sentencias break, lo cual hara ms ilegible el cdigo.
Funcin exit()
Esta funcin permite la finalizacin del programa en cualquier punto del mismo. Devuelve el control al sistema operativo o a otro proceso padre, enviando un valor de retorno. Necesita la inclusin del archivo de cabecera process.h, por medio de una sentencia #include. Si en un programa escribimos
68
Introduccin al Lenguaje C
if (condicin) exit (0); se produce el final del programa cuando condicin es cierta, en cuyo caso se devuelve el valor 0 al proceso padre. El programa que se muestra a continuacin (SINO.C) ser ejecutado desde un archivo BAT (proceso padre). El programa SINO pasar un valor de retorno al proceso padre por medio de la funcin exit(). El proceso padre inspecciona este valor de retorno mediante ERRORLEVEL.
Error! Marcador no definido./* Programa SINO.C: Admite por teclado el carcter 's' o el carcter 'n'. En el primer caso ('s') entrega un valor de retorno 1 En el segundo caso ('n') entrega un valor de retorno 0 */ #include <stdio.h> #include <conio.h> #include <process.h> void main () { char letra; do letra = getch (); while (letra != 's' && letra != 'n'); if (letra == 's') exit (1); exit (0); printf ("\nFin del programa"); } //Esta sentencia no se ejecuta nunca
Compilamos y enlazamos este programa como SINO.EXE y lo incluimos en un archivo por lotes como el siguiente:
Error! Marcador no definido.@ECHO OFF ECHO Pulsa S N SINO IF ERRORLEVEL == 1 GOTO SI GOTO NO :SI ECHO Pulsaste S GOTO FIN :NO ECHO Pulsaste NO :FIN @ECHO ON Este archivo visualiza el mensaje "Pulsaste S" cuando en SINO se teclea el carcter s, y visualiza el mensaje "Pulsaste NO" si en SINO se teclea el carcter n. En ningn caso se visualiza el mensaje "Fin del programa" de la ltima lnea de SINO.C
1. Introduccin
69
Ejercicios
1.
Escribe un programa que asigne una calificacin literal a un estudiante, basada en la siguiente tabla de puntuacin: 8.5 a 10 Sobresaliente 7 a 8.5 Notable 6 a 7 Bien 5 a 6 Suficiente 3.5 a 5 Insuficiente 0 a 3.5 Muy deficiente El programa capturar un valor numrico del teclado y visualizar la calificacin correspondiente. Los suspensos se mostrarn en amarillo parpadeando sobre fondo rojo, el sobresaliente en amarillo sobre fondo azul, y el resto en negro sobre fondo verde.
2.
Escribe un programa para determinar si un atleta es seleccionado para correr una maratn. Para seleccionar a un corredor, debe haber terminado una maratn anterior en un determinado tiempo. Los tiempos de calificacin son 150 minutos para hombres menores de 40 aos, 175 minutos para hombres mayores de 40 aos, y 180 minutos para mujeres. Los datos a introducir son: sexo (H/M), edad y tiempo efectuado en su anterior maratn. El programa visualizar el mensaje "Seleccionado" o "No seleccionado".
3.
Los empleados de una fbrica trabajan en dos turnos: diurno y nocturno. Se desea calcular el jornal diario de acuerdo al siguiente baremo: a) Las horas diurnas se pagan a 1000 pesetas. b) Las horas nocturnas se pagan a 1600 pesetas. c) Caso de ser domingo, la tarifa se incrementar en 400 pesetas el turno diurno y en 600 el nocturno.
4.
Escribe un programa que calcule e imprima la suma de los pares y de los impares comprendidos entre dos valores A y B que se introducen por teclado (A < B).
5.
Escribe un programa que calcule xn, siendo x y n dos nmeros enteros que se introducen por teclado.
6. 7.
Escribe un programa que calcule el factorial de un nmero entero positivo que se introduce por teclado. Escribe un programa que encuentre el primer valor N para el que la suma 1 + 2 + 3 + ... + N excede a un valor M que se introduce por teclado.
8.
Escribe un programa que calcule el primer elemento de la serie de Fibonacci que sea mayor o igual que un valor introducido por teclado. La serie de Fibonacci se define mediante:
70
Introduccin al Lenguaje C
a0 = a1 = 1
an = an-1 + an-2
9.
El valor de puede calcularse mediante la serie = 4 * ( 1 - 1/3 + 1/5 - 1/7 + 1/9 ...) Escribe un programa que calcule el valor de . Para elegir el nmero de trminos de la serie adopta el criterio de que la diferencia absoluta entre el valor real de (3.141592) y el valor calculado sea menor que 10-3. Crea una funcin que devuelva el valor absoluto de una expresin.
1. Introduccin
71
72
Introduccin al Lenguaje C
Al manejar funciones en C debemos tener en cuenta que a una funcin slo se puede acceder por medio de una llamada. Nunca se puede saltar de una funcin a otra mediante una sentencia goto. Tampoco est permitido declarar una funcin dentro de otra. En C, a diferencia de otros lenguajes, como Pascal, no existe distincin entre funciones y procedimientos. Por ello, una funcin puede estar o no dentro de una expresin. Si la funcin devuelve un valor, son vlidas sentencias del tipo: a = b * funcion (c, d); Pero tambin podemos encontrar funciones solas en una lnea de programa, funcion (a, b, c); incluso aunque la funcin devuelva algn valor. En ese caso la funcin realizara la tarea encomendada y el valor devuelto se perdera. Esto no provoca ningn error. Por ltimo, hemos de tener en cuenta que, aunque una funcin puede formar parte de una expresin compleja, nunca se le puede asignar un valor. Por tanto, es incorrecto escribir sentencias como funcion () = variable; que sera tan improcedente como escribir 5 = variable; El nico mtodo para que una funcin reciba valores es por medio de los parmetros formales.
Argumentos de funciones
Los parmetros formales son variables locales que se crean al comenzar la funcin y se destruyen al salir de ella. Al hacer una llamada a la funcin los parmetros formales deben coincidir en tipo y en nmero con las variables utilizadas en la llamada, a las que denominaremos argumentos de la funcin. Si no coinciden, puede no detectarse el error. Esto se evita usando los llamados prototipos de funciones que estudiaremos ms adelante, en este captulo. Los argumentos de una funcin pueden ser: valores (llamadas por valor) direcciones (llamadas por referencia)
1. Introduccin
73
void main () { int a = 3, b = 4; intercambia (a, b); printf ("\nValor de a: %d - Valor de b: %d", a, b); } void intercambia (int x, int y) { int aux; aux = x; x = y; y = aux; }
La salida de este programa es: Valor de a: 3 - Valor de b: 4 es decir, NO intercambia los valores de las variables a y b. Cuando se hace la llamada intercambia (a, b); se copia el valor de a en x, y el de b en y. La funcin trabaja con x e y, que se destruyen al finalizar, sin que se produzca ningn proceso de copia a la inversa, es decir, de x e y hacia a y b.
Error! Marcador no definido.#include <stdio.h> void main () { int a = 3, b = 4; intercambio (&a, &b); printf ("\nValor de a: %d - Valor de b: %d", a, b); } void intercambio (int *x, int *y)
74
Introduccin al Lenguaje C
Este programa S intercambia los valores de a y b, y muestra el mensaje Valor de a: 4 - Valor de b: 3 Adems, a diferencia del de la pgina 83, trabaja con direcciones. Como veremos en el siguiente captulo, en la declaracin void intercambio (int *x, int *y); los parmetros x e y son unas variables especiales, denominadas punteros, que almacenan direcciones de memoria. En este caso, almacenan direcciones de enteros.
Prototipos de funciones
Cuando una funcin devuelve un tipo no entero, antes de utilizarla, hay que "hacrselo saber" al resto del programa usando los prototipos de funciones. Un prototipo de funcin es algo que le indica al compilador que existe una funcin y cul es la forma correcta de llamarla. Es una especie de "plantilla" de la funcin, con dos cometidos:
1. Introduccin
75
identificar el tipo devuelto por la funcin. identificar el tipo y nmero de argumentos que utiliza la funcin. La forma de definir un prototipo de funcin es: tipo NombreFuncin (lista de tipos); Entre parntesis se ponen los tipos de los argumentos separados por comas (no es necesario poner el nombre) en el mismo orden en el que deben estar en la llamada. As, un prototipo como el siguiente float Ejemplo (int, char); indica que el programa va a utilizar una funcin llamada Ejemplo que devuelve, mediante una sentencia return, un valor de tipo float, y recibe dos valores en sus argumentos: el primero es un valor entero y el segundo un carcter. Tambin es un prototipo vlido float Ejemplo (int x, char y); En un programa, los prototipos de funciones se sitan antes de la funcin main(). A continuacin se muestra un programa que define un prototipo de funcin.
#include <stdio.h> float multiplica (float, float); main () { float a, b, total; printf ("\nTeclee dos nmeros: "); scanf ("%f %f", &a, &b); total = multiplica (a, b); printf ("\nEl producto de ambos es %f", total); } float multiplica (float m, float n) { return m * n; } /* Prototipo de la funcin multiplica() */
En este programa si en lugar de la lnea total = multiplica (a, b); escribisemos total = multiplica (a); el compilador, alertado por el prototipo, informara del error. Tanto en los prototipos como en las declaraciones de funciones, las referencias a cadenas de caracteres se realizan mediante la expresin char *. El significado de esta expresin se entender ms claramente en el prximo captulo. As, el prototipo de una funcin que devuelve una cadena de caracteres y tiene como nico argumento una cadena de caracteres es
76
Introduccin al Lenguaje C
char *Funcion (char *); Cuando una funcin tiene un nmero variable de argumentos, se especifica por medio de 3 puntos suspensivos. Este es el caso de la funcin printf() cuyo prototipo es: int printf (const char *, ...); La palabra reservada const aqu significa que la cadena referida con char * puede ser, bien una variable, como en printf (cadena); o bien una constante, como en printf ("Cadena constante"); Para indicar wque una funcin no tiene argumentos se pone la palabra reservada void entre los parntesis. Del mismo modo, se indica con void que una funcin no devuelve ningn valor (no hay sentencia return). Por ejemplo, la funcin de prototipo void funcion (void); no devuelve nada ni recibe valores. Cuando deseamos que una funcin devuelva una cadena de caracteres, el prototipo se escribe char *funcion (lista de parmetros); En el prximo captulo estudiaremos ms profundamente el significado de la expresin char *. Todos los programas que utilicen funciones de la biblioteca estndar deben incluir sus prototipos. Estos prototipos y otras definiciones usadas por las funciones estn en los archivos de cabecera como stdio.h, por lo que el programa deber tener las sentencias #include necesarias.
Recursividad
La recursividad es un concepto de lgica matemtica que consiste en definir una funcin en trminos de s misma. Por ejemplo, la definicin del factorial de un nmero es una definicin recursiva: n! = n (n - 1)! 0! = 1 En programacin una funcin es recursiva si puede llamarse a s misma. No todos los lenguajes permiten la recursividad. Una definicin recursiva de una funcin requiere dos partes: Una condicin de salida o clusula de escape. Un paso en el que los valores restantes de la funcin se definen en base a valores definidos anteriormente. La recursividad es una alternativa a la iteracin. Sin embargo, en general, una solucin recursiva es menos eficiente, en trminos de tiempo, que una solucin iterativa. Adems, las soluciones recursivas utilizan mucha memoria de pila, pues cada llamada a la funcin hace que se copie en la pila (cada vez) todo el juego de argumentos. Esto puede crear problemas cuando se llega a
1. Introduccin
77
niveles profundos de recursin. Cmo decidir cundo un problema se resuelve recursivamente o mediante iteraciones depende del problema en s. La solucin recursiva suele ser la ms sencilla, y debe elegirse si no hay grandes requerimientos de velocidad del proceso ni problemas con el tamao de la memoria. En otro caso debemos elegir la solucin iterativa, mucho ms rpida, aunque a menudo ms compleja. La siguiente funcin resuelve recursivamente el clculo del factorial long factorial (int n) { if (!n) return 1; return n * factorial (n - 1); } Representaremos ahora en un esquema el proceso de recursin al llamar, por ejemplo, a factorial(3).
factorial (3)
1 llamada a la funcin
3 * factorial (2)
2 llamada a la funcin
3*2=6
2 * factorial (1)
3 llamada a la funcin
2*1=2
1 * factorial (0)
Aqu se encuentra con la condicin de salida if (!n) return 1;
1*1=1
Se puede comparar este proceso con el que genera la solucin iterativa: long factorial (int n) { register int i; long total = 1; for (i = 1; i <= n; i++) total *= i; return total; } En el primer caso se hacen varias llamadas a la funcin, situando en la pila los valores correspondientes y "dejando pendientes" los clculos intermedios, hasta que se llega a la condicin de salida. En ese momento comienza el proceso inverso, sacando de la pila los datos previamente almacenados y solucionando los clculos que "quedaron sin resolver". Obviamente esta solucin es ms lenta que la iterativa en la que el resultado se obtiene con una sola llamada a la funcin y con menos requerimientos de memoria.
78
Introduccin al Lenguaje C
Sin embargo, mientras que en el caso del clculo del factorial de un nmero parece ms aconsejable la solucin iterativa, hay otros ejemplos en lo que ocurre lo contrario. Por ejemplo, la funcin de Ackerman se define del siguiente modo:
s = 0 s 0 y t = 0 s 0 y t 0
El diseo recursivo de esta funcin consiste, simplemente, en aplicar la definicin int Ack (int s, int t) { if (!s) return t + 1; else if (!t) return Ack (s - 1, 1); else return Ack (s - 1, Ack (s, t - 1)); } La solucin no recursiva de la funcin de Ackerman es mucho ms complicada.
La biblioteca de funciones
Hay una serie de funciones y macros definidas por el estndar ANSI que realizan tareas que facilitan enormemente la labor del programador. Adems, cada fabricante de software incluye sus propias funciones y macros, generalmente relacionadas con el mejor aprovechamiento de los ordenadores personales y del MS-DOS. Respecto a esto, debe tenerse en cuenta que la compatibilidad del cdigo slo est asegurado si se utilizan exclusivamente funciones pertenecientes al estndar ANSI. Como ya qued dicho, los prototipos de estas funciones, as como declaraciones de variables, macros y tipos de datos utilizados por ellas, estn definidos en los archivos de cabecera *.h. Por ello, es necesario incluirlos mediante sentencias #include cuando el programa hace uso de ellas. La tabla de la pgina siguiente muestra los archivos de cabecera estndar usados por Turbo C++. En el Captulo 13 se muestran algunas de las funciones de la biblioteca estndar. ARCHIVO LENGUAJE DE DESCRIPCIN CABECERA ALLOC.H Define funciones de asignacin dinmica de memoria ANSI C ASSERT.H Declara la macro de depuracin assert C++ BCD.H Define la clase bcd BIOS.H Define funciones utilizadas en rutinas de ROM-BIOS C++ COMPLEX.H Define las funciones matemticas complejas C++ CONIO.H Define varias funciones utilizadas en las llamadas a rutinas de E/S por consola en DOS ANSI C CTYPE.H Contiene informacin utilizada por las macros de conversin y clasificacin de caracteres DIR.H Contiene definiciones para trabajar con directorios. DOS.H Declara constantes y da las declaraciones necesarias para llamadas especficas del 8086 y del DOS ANSI C ERRNO.H Declara mnemnicos constantes para cdigos de error
1. Introduccin
79
ANSI C
C++ ANSI C
Declara constantes simblicas utilizadas en conexiones con la biblioteca de rutinas open() FLOAT.H Contiene parmetros para rutinas de coma flotante FSTREAM.H Define los flujos de C++ que soportan E/S de archivos GENERIC.H Contiene macros para declaraciones de clase genricas GRAPHICS.H Define prototipos para las funciones grficas IO.H Declaraciones de rutinas de E/S tipo UNIX IOMANIP.H Define los gestores de flujos de E/S de C++ y contiene macros para creacin de gestores de parmetros IOSTREAM.H Define rutinas bsicas de flujo de E/S de C++ (v2.0) LIMITS.H Parmetros y constantes sobre la capacidad del sistema LOCALE.H Define funciones sobre el pas e idioma MATH.H Define prototipos para las funciones matemticas MEM.H Define las funciones de gestin de memoria PROCESS.H Contiene estructuras y declaraciones para las funciones spawn(), exec() SETJMP.H Declaraciones para dar soporte a saltos no locales SHARE.H Parmetros utilizados en funciones que utilizan arhivos-compartidos SIGNAL.H Declara constantes y declaraciones para utilizarlos en funciones signal() y raise() STDARG.H Soporte para aceptar un nmero variable de argumentos STDDEF.H Declara varios tipos de datos y macros de uso comn STDIO.H Declara tipos y macros para E/S estndar STDIOSTR.H Declara las clases de flujo para utilizar con estructuras del archivo stdio.h STDLIB.H Define algunas de las rutinas comnmente utilizadas STREAM.H Define las clases de flujo de C++ para utilizarlas con arrays de bytes en memoria STRING.H Define varias rutinas de manipulacin de cadenas y de memoria SYS\STAT.H Declara constantes simblicas utilizadas para abrir y crear archivos SYS\TIMEB.H Define la funcin ftime() y la estructura timeb SYS\TYPES.H Define el tipo time_t TIME.H Estructuras y prototipos para funciones de tiempo VALUES.H Declara constantes dependientes de la mquina
FCNTL.H
Ejercicios
1.
El seno de un ngulo puede calcularse mediante la serie
sen ( x) = x -
3!
5!
7!
+ L
donde x se expresa en radianes ( radianes = 180 grados). Escribe un programa que calcule y muestre en pantalla el seno de un ngulo mediante la serie anterior. Finaliza el clculo en el trmino de la serie cuyo valor sea menor o igual a10-3. Para el clculo de xn crea una funcin no recursiva de prototipo
80
Introduccin al Lenguaje C
double potencia (duble x, int n); De igual modo, para el clculo de n! crea una funcin no recursiva de prototipo double factorial (int n); El ngulo debe ser un valor comprendido entre 0 y 360. En caso se enviar un mensaje a pantalla.
2.
Escribe un programa que calcule xn, siendo x y n dos enteros positivos que se introducen por teclado. Para el clculo crea una funcin recursiva de prototipo long potencia (int x, int n); que solucione el clculo.
3.
Escribe un programa que muestre los N primeros trminos de la sucesin de Fibonacci, utilizando una funcin recursiva int Fibonacci (int N); que devuelva el elemento N. El valor N se leer del teclado.
4.
Escribe un programa que muestre en pantalla la informacin del equipo proporcionada por las funciones de biblioteca biosequip() y biosmemory().
5.
Por medio de la funcin bioskey() construye un programa que muestre en pantalla el estado de pulsacin del teclado con el siguiente formato:
Maysculas derecha: SI/NO Maysculas izquierda: SI/NO Tecla Control: SI/NO Tecla Alt: SI/NO Tecla Bloq Despl: SI/NO Tecla Bloq Num: SI/NO Tecla Bloq Mays: SI/NO Tecla Ins: SI/NO NOTA: Para evitar el efecto que produce el cursor en la presentacin de la pantalla (hay que estar continuamente imprimiendo la informacin en la pantalla) elimnalo usando la funcin _setcursortype() de la biblioteca estndar.
6.
Escribe una funcin de prototipo char *Intro (int f, int c, int tam, char *cad); que utilice la funcin cgets() para capturar en la fila f, columna c, una cadena de caracteres de longitud mxima tam, y la almacene en cad. Devuelve cad.
1. Introduccin
81
7.
Escribe una funcin de prototipo int strdigit (char *cad); que inspeccione la cadena cad y devuelva 1 si cad est compuesta por dgitos numricos, y 0 si algn carcter de cad no es numrico. Utiliza la funcin isdigit().
8.
Escribe una funcin de prototipo char *Format_fecha (char *fecha, int tipo, char *format); que recibe en la cadena fecha una fecha con formato DDMMAAAA y pone en la cadena format esta fecha en un formato indicado por tipo. Los valores permitidos por tipo y sus formatos correspondientes se muestran en la siguiente tabla: tipo 0 1 2 3 format DD/MM/AA DD/MM/AAAA DD de MMMMMMMMMM de AAAA diasemana, DD de MMMMMMMMMM de AAAA
Si fecha no es una fecha vlida o tipo est fuera de rango, format ser la cadena nula. Devuelve format. Utiliza la funcin sprintf().
9.
Escribe una funcin de prototipo int Valida_fecha (char *fecha); que recibe en la cadena fecha una fecha en formato DDMMAAAA y devuelve un valor de 0 a 6 si la fecha es correcta, indicando con los valores 0 a 6 el da de la semana a que corresponde fecha (0=domingo, 1=lunes, ...). Si la fecha no es correcta, devolver -1. Utiliza la funcin intdos para hacer las llamadas a las funciones 2Ah y 2Bh de la INT 21h.
10.
Escribe una funcin de prototipo int Tecla (int *scan); que captura una pulsacin de tecla y devuelve en scan el cdigo de exploracin, y mediante return, su cdigo ASCII. Utiliza la funcin bioskey().
11.
Escribe una funcin de prototipo int Mensaje (int st, int fil, int col, char *cad); que presenta en pantalla la cadena cad en la fila fil, columna col. Si st vale 1, espera a que se pulse una tecla, devolviendo mediante return su cdigo ASCII si se puls una tecla normal, y 1000+cdigo de exploracin si se puls una tecla especial. Si st vale 0, no espera a que se pulse ninguna tecla.
82
Introduccin al Lenguaje C
1386
1387
1388 1393
1393 dato
1394
1395
El puntero almacenado en la posicin de memoria 1388 apunta al dato almacenado en la posicin de memoria 1393
Las variables puntero se declaran de la siguiente forma: tipo *nombre; siendo nombre el identificador de la variable puntero, y tipo el tipo de variable a la que apunta. Por ejemplo, char *m;
1. Introduccin
83
int *n; float *p; En estas declaraciones, las variables m, n y p son punteros que apuntan, respectivamente, a datos de tipo char, int y float. Es importante darse cuenta de que ni m es una variable de tipo char, ni n de tipo int, ni p de tipo float. Los tipos definen el tipo de dato al que apunta el puntero
m 189
187
188
189
190
191
192
193
194
n 189
187
188
189
190
191
192
193
194
p 189
187
188
189
190
191
192
193
194
Los operadores de punteros son los que estudiamos en el Captulo 2, y se definieron en la pgina 22, es decir, * * & direccin de * en la direccin de
El operador * slo se puede aplicar a punteros. Despus de float m; float *p; ... ... m = *p; la variable m almacena el contenido de la direccin apuntada por p. Del mismo modo despus de *p = m; el valor de m se almacena en la direccin apuntada por p. La siguiente asignacin int valor = 100, q; ... ... q = valor; puede conseguirse tambin mediante int valor = 100, q, *m; ... ... m = &valor; q = *m;
84
Introduccin al Lenguaje C
es decir, se almacena en la variable q el valor 100. El cdigo de formato para visualizar variables puntero mediante funciones tipo printf() es %p. As, int *m; ... ... printf ("%p", m); muestra en pantalla la direccin almacenada por m. La visualizacin se hace en hexadecimal. Otro cdigo de formato relacionado con punteros es %n. Este cdigo da lugar a que el nmero de caracteres que se han escrito en el momento en que se encuentra %n, se asocien a una variable cuya direccin se especifica en la lista de argumentos. El siguiente programa muestra en pantalla la frase Se han escrito 11 caracteres
#include <stdio.h> #include <conio.h> void main (void) { int p; clrscr (); printf ("Hola mundo %n", &p); printf ("\nSe han escrito %d caracteres", p); }
Las operaciones permitidas con punteros son: Asignacin Incremento / Decremento Suma / Resta Comparacin Vamos a describir cada una de ellas.
Asignacin
Dadas las declaraciones float x; float *p, *q; la forma de asignar a p y q la direccin de x es: p = &x; q = &x; Ahora p y q almacenan la misma direccin de memoria: la de la variable x. El mismo efecto se consigue con la asignacin directa entre punteros: p = &x;
1. Introduccin
85
q = p; No es correcta una sentencia como p = x; puesto que x es una variable de tipo float (almacena un dato float), mientras que p almacena la direccin de un dato de tipo float. Por medio de punteros pueden hacerse asignaciones de contenidos. Por ejemplo: float a, b; float *p, *q; ... ... a = 1.5; p = &b; q = &a; *p = *q; En esta secuencia, p y q almacenan valores diferentes (la direccin de b y la direccin de a, respectivamente). La ltima sentencia asigna contenidos, es decir, almacena en el lugar apuntado por p (la variable b) lo que hay en el lugar apuntado por q (la variable a). Es, por tanto, equivalente a b = a;
Incremento / Decremento
Para comprender estas operaciones, debemos tener en cuenta que hacen referencia a elementos de memoria y no a direcciones. Esto quiere decir que los operadores ++ y -- actan de modo diferente segn el tipo apuntado por el puntero. Si p es un puntero a caracteres (char *p) la operacin p++ incrementa el valor de p en 1. Si embargo, si p es un puntero a enteros (int *p), la misma operacin p++ incrementa el valor de p en 2 para que apunte al siguiente elemento, pues el tipo int ocupa dos bytes. Del mismo modo, para el tipo float la operacin p++ incrementa el valor de p en 4. Lo dicho para el operador ++ se cumple exactamente igual, pero decrementando, para el operador --.
Suma / Resta
Ocurre exactamente lo mismo que con las operaciones de incremento y decremento. Si p es un puntero, la operacin p = p + 5; hace que p apunte 5 elementos ms all del actual. Si p estaba definido como un puntero a caracteres, se incrementar su valor en 5, pero si estaba definido como un puntero a enteros, se incrementar en 10.
Comparacin
86
Introduccin al Lenguaje C
Pueden compararse punteros del mismo modo que cualquier otra variable, teniendo siempre presente que se comparan direcciones y no contenidos. int *p, *q; ... ... if (p == q) puts ("p y q apuntan a la misma posicin de memoria"); ... ... if (*p == *q) puts ("Las posiciones apuntadas por p y q almacenan el mismo valor"); En el segundo caso puede aparecer el mensaje aunque p y q apunten a direcciones diferentes.
Matrices unidimensionales
Son aquellas que slo precisan de un ndice para acceder a cada elemento. Tambin se les llama vectores o listas. Todos los elementos de un vector se almacenan en posiciones de memoria contiguas, almacenndose el primer elemento en la direccin ms baja. Un vector se declara de la siguiente forma: tipo nombre[num_elem]; donde tipo es el tipo de dato de todos los elementos del vector, nombre es cualquier identificador vlido C, y num_elem es el nmero de elementos del vector. Por ejemplo, char frase[20]; int numero[16]; float valor[12]; declaran, por este orden, un vector de 20 caracteres, otro de 16 elementos enteros y otro de 12 elementos en coma flotante. El nmero de bytes ocupado por una matriz se calcula multiplicando el nmero de elementos por el tamao de cada uno de ellos. As, los 3 vectores anteriores ocupan, respectivamente, 20, 32 y 48 bytes. En las declaraciones de matrices, el nombre de la matriz sin ndices es un puntero al primer elemento. As, en las declaraciones anteriores frase es un puntero a char y almacena la direccin de frase[0]. Lo mismo ocurre para numero y valor. En un vector de N elementos, el primero se referencia con el ndice 0 y el ltimo con el ndice N-1. As, en el vector int numero[16]; el primer elemento es numero[0] y el ltimo numero[15]. Es importante tener en cuenta que C no hace comprobacin de lmites en el proceso de matrices. El control debe hacerlo el programador. As, es posible escribir numero[30] = 250;
1. Introduccin
87
manejando el elemento 30 del vector numero que se declar para almacenar slo 16 elementos. Las consecuencias para el programa suelen ser desastrosas. En el siguiente programa se carga un vector de 10 caracteres desde el teclado, y se muestra despus la direccin y contenido de cada elemento.
Error! Marcador no definido.#include <stdio.h> #include <conio.h> void main (void) { register int i; char vector[10]; for (i = 0; i <= 9; i++) vector[i] = getche ();
for (i = 0; i <= 9; i++) printf ("\nLugar: %d - Direccin: %p - Valor: %c", i, vector + i, vector[i]);
Cadenas de caracteres
Un caso particular de vector es la cadena de caracteres. Una cadena de caracteres se declara mediante char nombre[num_car]; y permite almacenar num_car-1 caracteres y el carcter nulo '\0' de terminacin. Por lo tanto, una declaracin como char frase[21]; es apta para almacenar 20 caracteres y el nulo. C permite la inicializacin de cadenas de caracteres en la declaracin, mediante sentencias del tipo char cadena[ ] = "Esto es una cadena de caracteres"; en la que no es necesario aadir el nulo final ni indicar el tamao, pues lo hace automticamente el compilador. Las operaciones con cadenas de caracteres como copiar, comparar, concatenar, medir, etc., se hacen mediante funciones de la biblioteca estndar. Su utilizacin requiere incluir el archivo de cabecera string.h. Veamos alguna de ellas.
88
Introduccin al Lenguaje C
printf ("\nTeclee una frase: "); gets (cad1); printf ("\nTeclee otra frase: "); gets (cad2); strcat (cad1, cad2); puts (cad1); En este ejemplo, si se teclea Primera frase para cad1 y Segunda frase para cad2, se muestra en pantalla Primera fraseSegunda frase Tendra el mismo efecto puts (strcat (cad1, cad2)); Hay que asegurarse de que el tamao de cad1 es suficiente para almacenar el resultado.
<0 0 >0
Ejemplo:
1. Introduccin
89
char cad1[40], cad2[40]; ... ... printf ("\nTeclee una frase: "); gets (cad1); printf ("\nTeclee una frase: "); gets (cad2); n = strcmp (cad1, cad2); if (!n) puts ("Son iguales"); else if (n < 0) puts ("La primera es menor que la segunda"); else puts ("La primera es mayor que la segunda"); Es necesario insistir en que la comparacin no se hace en cuanto al tamao de la cadena sino en cuanto al orden de los caracteres en el cdigo ASCII. Esto quiere decir que si cad1 es ABCDE y cad2 es xyz, la funcin strcmp devuelve un valor negativo, pues se considera que cad1 < cad2 ya que el carcter A tiene un cdigo ASCII menor que el carcter x.
90
Introduccin al Lenguaje C
... printf ("\nTeclee una frase en maysculas: "); gets (cad); printf ("\nLa cadena en minsculas es %s", strlwr (cad));
1. Introduccin
91
Devuelve 1 si ch es alfanumrico (letra del alfabeto o dgito) y 0 en caso contrario. Devuelve 1 si ch es una letra del alfabeto y 0 en caso contrario. Devuelve 1 si ch es un dgito del 0 al 9, y 0 en caso contrario. Devuelve 1 si ch es un letra minscula y 0 en caso contrario. Devuelve 1 si ch es una letra mayscula y 0 en caso contrario. Devuelve el carcter ch en minscula. Si ch no es una letra mayscula la funcin devuelve ch sin modificacin. Devuelve el carcter ch en mayscula. Si ch no es una letra minscula la funcin devuelve ch sin modificacin.
int isalpha (int ch) int isdigit (int ch) int islower (int ch) int isupper (int ch) int tolower (int ch)
En el Captulo 13 se muestran ms funciones de caracteres. El programa siguiente hace uso de alguna de las funciones anteriores para examinar una cadena de caracteres y convertir las minsculas a maysculas y viceversa. Adems cuenta cuntos caracteres son dgitos numricos.
Error! Marcador no definido.#include <stdio.h> #include <conio.h> #include <string.h> #include <ctype.h> void main (void) { char cadena[100]; int contador = 0; register int i; clrscr (); printf ("\nTeclee una cadena de caracteres: "); gets (cadena); for (i = 0; i <= strlen (cadena); i++) { if (isupper (cadena[i])) cadena[i] = tolower (cadena[i]); else if (islower (cadena[i])) cadena[i] = toupper (cadena[i]); else if (isdigit (cadena[i])) contador++; } printf ("\nLa cadena tiene %d dgitos numricos\n", contador); puts (cadena); }
Punteros y matrices
Puesto que una matriz se identifica mediante la direccin del primer elemento, la relacin entre punteros y matrices es estrecha. Sea la declaracin int matriz[100];
92
Introduccin al Lenguaje C
Podemos identificar un elemento, digamos el 25, de cualquiera de las dos formas siguientes: matriz[24]; *(matriz + 24); puesto que matriz (sin ndices) es la direccin del primer elemento. El uso de punteros para manejar matrices es, en general, ms eficiente y rpido que el uso de ndices. La decisin de qu mtodo utilizar (punteros o ndices) depende del tipo de acceso. Si se va a acceder a los elementos de la matriz de forma aleatoria, es mejor utilizar ndices. Sin embargo, si el acceso va a ser secuencial, es ms adecuado usar punteros. Este segundo caso es el tpico en las cadenas de caracteres. Por ejemplo, las sentencias siguientes muestran en pantalla, carcter a carcter una cadena de caracteres. char *p, cadena[30]; register int i; ... ... for (i = 0; cadena[i]; i++) putch (cadena[i]); ... ... p = cadena; for (; *p; p++) putch (*p); ... ... p = cadena; while (*p) putch (*p++); Las dos ltimas son las ms adecuadas. Otro ejemplo lo constituyen las 3 funciones que se describen a continuacin. Estas funciones comparan dos cadenas de caracteres de modo similar a como lo hace strcmp().
int compara1 (char *cad1, char *cad2) { register int i; for (i = 0; cad1[i]; i++) if (cad1[i] - cad2[i]) return (cad1[i] - cad2[i]); return 0; }
int compara2 (char *cad1, char *cad2) { char *p1, *p2; p1 = cad1; p2 = cad2; while (*p1) { if (*p1 - *p2) return (*p1 - *p2); else { p1++; p2++; } } return 0; }
1. Introduccin
93
int compara3 (char *cad1, char *cad2) { char *p1, *p2; p1 = cad1; p2 = cad2; for (; *p1; p1++, p2++) if (*p1 - *p2) return (*p1 - *p2); return 0; }
Matrices bidimensionales
Una matriz bidimensional es aquella que necesita dos ndices para identificar un elemento. Puede decirse que una matriz bidimensional es una estructura de datos organizados en filas y columnas. Se declaran de la siguiente forma: tipo nombre[n filas][n columnas]; Por ejemplo, para declarar una matriz de nmeros enteros organizada en 8 filas y 6 columnas se escribe int total[8][6]; El primer elemento de la matriz es total[0][0] y se almacena en una direccin de memoria identificada por total. El ltimo es total[7][5]. Un caso particular de matrices bidimensionales lo constituyen las matrices de cadenas de caracteres. Por ejemplo, la sentencia char cadenas[10][25]; declara una matriz de 10 cadenas de 24 caracteres ms el nulo. Para acceder a una cadena en particular basta especificar el ndice izquierdo (nmero de cadena). As, gets (cadena[6]); lee del teclado una cadena de caracteres y la almacena en la sptima cadena (la primera es cadena[0]) de la matriz . Esta sentencia es equivalente a gets (&cadena[6][0]); Para acceder a un carcter concreto de una cadena hay que especificar ambos ndices. Por ejemplo, la sentencia cadena[3][9] = 'X'; almacena en el 10 carcter de la 4 cadena el carcter X.
94
Introduccin al Lenguaje C
Error! Marcador no definido.//Cambia en un grupo de cadenas las vocales por # #include <stdio.h> #include <conio.h> #include <string.h> void main (void) { char cadena[10][70]; register int i, j; clrscr (); for (i = 0; i <= 9; i++) { printf ("\nCadena n %d: ", i); gets (cadena[i]); } for (i = 0; i <= 9; i++) { for (j = 0; cadena[i][j]; j++) { if (strchr ("aeiouAEIOU", cadena[i][j])) cadena[i][j] = '#'; } puts (cadena[i]); } } En el siguiente programa se carga del teclado una matriz de 10 cadenas de caracteres y se busca en ellas la primera aparicin de la cadena HOLA, informando del nmero de cadena en que se encuentra y de la posicin que ocupa en ella. Para ello se utiliza la funcin strstr explicada en el Captulo 13 (pgina 212).
Error! Marcador no definido.#include <stdio.h> #include <conio.h> #include <string.h> void main (void) { char cadena[10][50]; register int i; char *p; clrscr (); for (i = 0; i <= 9; i++) { printf ("\nCadena n %d: ", i); gets (cadena[i]); strupr (cadena[i]); } for (i = 0; i <= 9; i++) { p = strstr (cadena[i], "HOLA"); if (p) printf ("\nCadena n %d, posicin %d", i, p - cadena[i]); } }
Podemos acceder a los elementos de una matriz bidimensional mediante punteros. Se accede al elemento matriz[i][j] mediante la frmula: *(matriz + i * n_de_filas + j)
1. Introduccin
95
Matrices de ms de 2 dimensiones
En C pueden manejarse matrices de ms de 2 dimensiones. Para declarar estas matrices se hace tipo nombre[tamao 1][tamao 2] ... [tamao N]; La siguiente sentencia declara una matriz tridimensional de elementos de tipo float: float matriz3D[5][3][8]; El problema de este tipo de matrices es la cantidad de memoria que pueden llegar a ocupar, ya que esta aumenta exponencialmente con el nmero de dimensiones. Es por ello que para estas estructuras suele utilizarse asignacin dinmica de memoria, de modo que permite asignar o recortar memoria segn se va necesitando. Estudiaremos esto en el Captulo 9. Las siguientes sentencias muestran como se puede cargar desde el teclado la matriz matriz3D. for (i = 0; i <= 4; i++) { for (j = 0; j <= 5; j++) { for (k = 0; k <= 7; k++) { printf ("\nElemento %d-%d-%d: ", i, j, k); scanf ("%f", &matriz3D[i][j][k]); } } }
El mismo efecto se produce usando anidamiento de llaves: int tabla[2[3] = { { 5, 62, 34 }, { 21, 43, 90 } };
96
Introduccin al Lenguaje C
char frases[3][30] = {
En todas las matrices multidimensionales puede omitirse el primer ndice cuando se inicializan en la declaracin. Por ello, las declaraciones siguientes: int tabla[3][4] = { 6, 12, 25, 4, 5, 13, 7, 2, 2, 4, 9, 6}; int tabla[ ][4] = {6, 12, 25, 4, 5, 13, 7, 2, 2, 4, 9, 6}; son idnticas. En el segundo caso, el compilador se encarga de calcular el tamao correspondiente.
En cualquiera de los 2 casos el resultado es idntico, pues ambas declaraciones le indican al compilador que se va a recibir en el argumento la direccin de un entero (la del primer elemento de la matriz). En el primer caso, el valor 30 no se tiene en cuenta. El siguiente programa carga desde el teclado una matriz de enteros y muestra el mayor de ellos, calculndolo mediante una funcin.
Error! Marcador no definido.#include <stdio.h> void main (void) { register int i; int vector[30]; for (i = 0; i <= 29; i++) { printf ("\nElemento %d: ", i); scanf ("%d", &vector[i]); } printf ("\nEl mayor es %d", mayor (vector, 30));
1. Introduccin
97
} int mayor (int tabla[ ], int num_element) { register int i; int max; max = tabla[0]; for (i = 0; i < num_element; i++) if (max < tabla[i]) max = tabla[i]; return max; }
Ntese que es necesario pasar el tamao de la matriz como argumento, pues de lo contrario la funcin mayor() no tendra ninguna informacin sobre el nmero de elementos de la matriz. Para las matrices bidimensionales, aunque se pasa slo la direccin del primer elemento, es necesario especificar el nmero de columnas (segundo ndice) para que el compilador sepa la longitud de cada fila. Por ejemplo, una funcin que reciba como argumento una matriz declarada como int matriz2D[10][20]; se declara de la forma siguiente: funcion (int x[ ][20]) { ... } y la llamada se hace con una sentencia como funcion (matriz2D); En general, para matrices multidimensionales, slo se puede omitir el primer ndice en la declaracin. Por ejemplo, una matriz declarada mediante int matriz3D[5][10][20]; se pasa como argumento de una funcin mediante una llamada de la forma funcion (matriz3D); y la declaracin de la funcin tiene el siguiente aspecto: funcion (int x[ ][10][20]) { ... }
98
Introduccin al Lenguaje C
Para hacer esto en cualquier programa C, es necesario modificar la forma en que se llama a la funcin main(), incluyendo en ella argumentos que permitan la obtencin de los parmetros introducidos en la lnea de rdenes. La forma de hacer esto es la siguiente: main (int argc, char *argv[ ]) Veamos el significado de las variables argc y argv. argc: argv[ ]: Entero que indica el nmero de parmetros tecleados (incluye el nombre del programa). Matriz de cadenas de caracteres. Cada uno de los elementos argv[i] es una cadena que almacena un argumento.
La variable argc vale 1 como mnimo, puesto que se cuenta el nombre del programa. Los parmetros se identifican mediante argv de la siguiente manera: ... ... argv[0] cadena que almacena el nombre del programa. argv[1] cadena que almacena el primer parmetro. argv[2] cadena que almacena el segundo parmetro.
Para que los argumentos sean tratados como diferentes tienen que ir separados por uno o varios espacios blancos. As, en el ejemplo anterior la variable argc vale 4 (nombre del programa y 3 parmetros). Si escribimos, por ejemplo C:\>PROG PAR1,PAR2 la variable argc valdra 2, puesto que la cadena PAR1,PAR2 queda identificada como un slo parmetro (almacenado en la cadena argv[1]) ya que la coma no acta como separador. Para que PAR1 y PAR2 sean tratados como dos parmetros diferentes, debe ejecutarse PROG mediante C:\>PROG PAR1 PAR2 El siguiente programa lista los parmetros, si los hay, de la lnea de rdenes. #include <stdio.h> void main (int argc, char *argv[ ]) { register int i; printf ("\nNombre del programa: %s", argv[0]); if (argc == 1) printf ("\nNo se han introducido parmetros"); else { printf ("\nParmetros en la lnea de rdenes: "); for (i = 1; i < argc; i++) printf ("\n%d: %s", i, argv[i]); } }
La funcin main() soporta una variable ms, llamada env: env[ ]: Matriz de cadenas de caracteres. Cada uno de los elementos de la matriz es una cadena que almacena el nombre y contenido de una variable del entorno.
1. Introduccin
99
#include <stdio.h> void main (int argc, char *argv[ ], char *env[ ]) { register int i; printf ("\nNombre del programa: %s", argv[0]); if (argc == 1) printf ("\nNo se han introducido parmetros"); else { printf ("\nParmetros en la lnea de rdenes: "); for (i = 1; i < argc; i++) printf ("\n%d: %s", i, argv[i]); } printf ("\nVariables de entorno: \n"); for (i = 0; env[i]; i++) puts (env[i]); }
C:\> PROG proporciona la salida Nombre del programa: PROG Nombre del programa: PROG Parmetros en la lnea de rdenes: No se han introducido parmetros 1: PAR1 Variables de entorno: 2: PAR2 COMSPEC=C:\DOS\COMMAND.COM Variables de entorno: PROMPT=$P$G
Matrices de punteros
Pueden definirse matrices de punteros, es decir, matrices cuyos elementos son direcciones. Por ejemplo, int *pint[20]; char *pchar[40]; declaran una matriz pint de 20 punteros a enteros y otra matriz pchar de 40 punteros a caracteres. Para asignar las direcciones se hace igual que con cualquier otro puntero. Mediante las siguientes sentencias int *pint[20], a; ... ... a = 20; pint[3] = &a; printf ("%d", *pint[3]); se muestra en pantalla el nmero 20. Un ejemplo tpico de matriz de punteros es la matriz de cadenas de caracteres. Las declaraciones siguientes son equivalentes char dias[ ][10] = { "Domingo", "Lunes", "Martes", "Mircoles",
100
Introduccin al Lenguaje C
"Jueves", "Viernes", "Sbado" }; char *dias[ ] = { "Domingo", "Lunes", "Martes", "Mircoles", "Jueves", "Viernes", "Sbado" };
Punteros a punteros
En la mayora de los casos un puntero apunta a un dato.
p Direccin de x
x Valor
p1 Direccin de p2
p2 Direccin de x
x Valor
En este caso *p2 proporciona el valor de x. Pero p1 proporciona la direccin de p2. Para acceder a x mediante p1 hay que hacer **p1.
#include <stdio.h> void main (void) { int x, *p, **q; x = 10; p = &x; q = &p; printf ("%d", **q); }
1. Introduccin
101
Punteros a funciones
Puesto que una funcin ocupa una posicin en la memoria, pueden definirse punteros a funciones, y hacer llamadas a la funcin por medio de la direccin. Esto se aplica generalmente para trabajar con matrices de funciones. Vemoslo con un ejemplo. El siguiente programa realiza las operaciones bsicas suma, resta y producto, por medio de llamadas a funciones que forman parte de una matriz.
#include <stdio.h> #include <conio.h> int menu (void); int suma (int, int); int resta (int, int); int producto (int, int); int (*calculo[3]) (int, int) = { suma, resta, producto }; void main (void) { int n, x, y, resultado; while (( n = menu()) != 3) { printf ("\nTeclea dos nmeros: "); scanf ("%d %d", &x, &y); resultado = (*calculo[n]) (x, y); printf (" = %d", resultado); getch (); } } int menu (void) { int opcion; clrscr (); puts ("0. Suma"); puts ("1. Resta"); puts ("2. Producto"); puts ("3. Salir"); do { opcion = getch (); } while (opcion < '0' || opcion > '3'); return opcion - 48; } int suma (int a, int b) { printf ("\n%d + %d", a, b); return a + b; } int resta (int a, int b) {
102
Introduccin al Lenguaje C
printf ("\n%d - %d", a, b); return a - b; } int producto (int a, int b) { printf ("\n%d * %d", a, b); return a * b; }
En general, un puntero a una funcin se declara de acuerdo a tipo (*pfunc) (); siendo pfunc el nombre del puntero, y tipo el tipo del valor devuelto por la funcin. Los parntesis son necesarios, pues tipo *pfunc (); declara una funcin que devuelve un puntero. Para llamar a la funcin debe hacerse (*pfunc) ();
1. Introduccin
103
Ejercicios
1.
Carga mediante el teclado una matriz entera de 4 filas y 3 columnas. Calcula y muestra en pantalla la suma de cada fila y de cada columna por medio de dos funciones de prototipos int suma_fila (int fila); int suma_columna (int columna);
2.
Construye un programa que cree y muestre en pantalla un cuadrado mgico de orden 3. Un cuadrado mgico es aqul en el que todas las filas y columnas suman lo mismo. Para ello, se coloca el valor 1 en el medio de la 1 fila. Los siguientes valores (2, 3, 4, ...) se sitan en la fila de arriba, columna de la derecha, salvo que est ocupada, en cuyo caso se coloca inmediatamente debajo. Se supone que las filas y columnas de los extremos son adyacentes. 1 3 2 3 4 1 5 6 2 8 3 4 1 5 9 6 7 2
3.
Construye un programa que cree y muestre en pantalla un cuadrado mgico de orden N impar (3 < N 19). El valor de N se leer de la lnea de rdenes (declarando adecuadamente la funcin main). Enva a pantalla un mensaje de error en cada uno de los siguientes casos: El valor de N est fuera de rango. No se ha dado valor a N en la lnea de rdenes. Hay demasiados parmetros en la lnea de rdenes. En cualquiera de los casos finalizars el programa despus de enviado el mensaje. (NOTA: Utiliza la funcin atoi() de la biblioteca estndar).
4.
Construye un programa que cargue del teclado dos matrices enteras A y B de orden 3x3, y que visualice la matriz producto P = A B.
p = a b
ij k =0 ik
kj
La carga de matrices debes realizarla mediante una funcin a la que llamars dos veces: una para cargar A y otra para cargar B. Tambin calculars los elementos pij mediante una funcin.
5.
Construye dos funciones similares a strlwr() y strupr() que acten tambin sobre los caracteres , , letras acentuadas, etc. Se llamarn strminus() y strmayus().
6.
Desarrolla las siguientes funciones: Una funcin llamada burbuja() que ordene ascendentemente un vector de 100 elementos enteros por el mtodo de la burbuja. Una funcin llamada seleccion() que ordene ascendentemente un vector de 100 elementos enteros por el mtodo de seleccin. Una funcin llamada insercion() que ordene ascendentemente un vector de 100 elementos enteros por el mtodo de insercin.
104
Introduccin al Lenguaje C Una funcin llamada shell() que ordene ascendentemente un vector de 100 elementos enteros por el mtodo shell. Una funcin llamada quick() que ordene ascendentemente un vector de 100 elementos enteros por el mtodo de ordenacin rpida o Quick Sort. Construye un programa que cargue un vector ordenado descendentemente a[0] = 99 a[1] = 98 a[2] = 97 ... a[99] = 0
y que determine, mediante llamadas a las funciones anteriores, qu mtodo de ordenacin es ms rpido. Para medir el tiempo utiliza la funcin de la biblioteca estndar biostime() cuyo prototipo est en bios.h.
7.
Construye dos funciones de prototipos char *Copia (char *cad1, const char *cad2); char *Concatena (char *cad1, const char *cad2); que realicen las mismas operaciones, respectivamente, que las funciones de biblioteca estndar strcpy() y strcat(). Haz dos versiones de cada funcin: una con ndices y otra con punteros.
8.
Construye una funcin de prototipo char *Insertar (char *cad, int car, int pos); que inserte el carcter car en la posicin pos de la cadena cad. La funcin debe devolver la direccin de cad. Haz dos versiones de la funcin: una con ndices y otra con punteros.
9.
Construye una funcin de prototipo char *Elimina (char *cad, int pos); que elimine de cad el carcter situado en la posicin pos. La funcin debe devolver la direccin de cad. Haz dos versiones de la funcin: una con ndices y otra con punteros.
10.
Construye una funcin de prototipo char *Substr (char *orig, int desde, int n, char *dest); que almacene en la cadena dest la subcadena de cad que comienza en la posicin desde y tiene n caracteres. La funcin debe devolver la direccin de dest. Haz dos versiones de la funcin: una con ndices y otra con punteros.
11.
Crea un vector de 1000 elementos enteros ordenados descendentemente: x[0] 999 x[1] 998 x[2] 997 ... ... x[998] 1 x[999] 0
1. Introduccin
105
y ordnalo ascendentemente por el mtodo de la burbuja. Haz dos versiones del programa: una con ndices y otra con punteros. Mide la eficiencia de cada mtodo utilizando la funcin clock() de la biblioteca estndar.
12.
Utilizando punteros carga un vector de 50 elementos enteros (Genera los valores aleatoriamente mediante las funciones de la biblioteca estndar random() y randomize()). Posteriormente muestra cul es el valor mayor, su posicin en el vector y la posicin de memoria que ocupa.
13.
Haz un programa que cree un vector preparado para almacenar 50 datos enteros que funcione con estructura de pila. El programa ejecutar las siguientes acciones: Mostrar el contenido de la pila. Introducir un dato en la pila. Sacar un dato de la pila. Fin del programa. El programa presentar este men y realizar las acciones deseadas mediante funciones agrupadas en una matriz.
106
Introduccin al Lenguaje C
1. Introduccin
107
Estructuras
Una estructura es un conjunto de variables de igual o distinto tipo, agrupadas bajo un nombre comn. A esas variables se les denomina campos de la estructura. Las estructuras se declaran mediante la palabra reservada struct. Hay varias formas de declarar una estructura, pero la ms general es la siguiente: struct nombre_estructura { tipo variable1; tipo variable2; ... ... tipo variableN; }; Veamos un ejemplo. La siguiente declaracin struct FICHA { char nombre[40]; int edad; float altura; }; define una estructura llamada FICHA con tres campos de distinto tipo: nombre, edad y altura. Hemos de tener en cuenta que la declaracin anterior slo define la forma de la estructura, pero no declara ninguna variable con dicha forma. Por as, decirlo, hemos definido una plantilla de 3 campos, pero no hemos aplicado dicha plantilla a ninguna variable. Para hacerlo hay que escribir sentencias como struct FICHA registro; que declara una variable llamada registro con la estructura FICHA, es decir, registro es una variable compuestas por 3 campos: nombre, edad y altura. Para acceder a cada uno de los campos se utiliza el operador punto (.) de la siguiente forma: strcpy (registro.nombre, "Jos Garca"); registro.edad = 38; registro.altura = 1.82; Por supuesto, pueden declararse varias variables con la misma estructura: struct FICHA var1, var2; que son dos variables con la estructura FICHA. Los campos de cada una de ellas, como por ejemplo var1.edad y var2.edad son dos variables distintas que ocupan posiciones de memoria diferentes. Tambin pueden declararse las variables a la vez que se define la estructura: struct FICHA { char nombre[40]; int edad; float altura; } var1, var2;
108
Introduccin al Lenguaje C
que declara dos variables var1 y var2 con la estructura FICHA. Este tipo de declaraciones no impide que puedan declararse ms variables con esa estructura: #include <... struct FICHA { char nombre[40]; int edad; float altura; } var1, var2; void Funcion (void); void main (void) { struct FICHA var3; ... ... } void Funcion (void) { struct FICHA var4; ... ... } En las sentencias anteriores se declaran 4 variables con la estructura FICHA: var1 y var2, globales; var3, local a main(); y var4, local a Funcion(). Pueden hacerse declaraciones de una estructura sin darle nombre. Por ejemplo: struct { char nombre[40]; int edad; float altura; } var1, var2; declara las variables var1 y var2 con la estructura indicada. El problema que plantea este tipo de declaraciones es que, al no tener nombre la estructura, no es posible declarar otras variables con esa estructura. Tambin es habitual definir las estructuras con un nuevo tipo. Por ejemplo: typedef struct { char nombre[40]; int edad; float altura; } MIESTR; Ahora podemos hacer uso del nuevo tipo para declarar variables mediante sentencias como: MIESTR var1, var2; Fijmonos que, debido a la sentencia typedef, MIESTR no es una variable, sino un tipo de dato.
1. Introduccin
109
Inicializacin de estructuras
Es posible inicializar variables con estructura en el momento en que son declaradas. Por ejemplo: #include <... struct FICHA { char nombre[40]; int edad; float altura; }; void main (void) { struct FICHA registro = { "Jos Garca", 38, 1.82 }; ... que produce el mismo efecto que las sentencias strcpy (registro.nombre, "Jos Garca"); registro.edad = 38; registro.altura = 1.82;
Anidamiento de estructuras
Es posible el anidamiento de estructuras, en el sentido de que un campo de una estructura puede ser, a su vez, una estructura. Vemoslo con un ejemplo: struct FECHA { int dia; int mes; int anyo; }; struct CUMPLE { char nombre[40]; struct FECHA nacim; }; struct CUMPLE aniversario; La variable aniversario as definida tiene los siguientes campos: aniversario.nombre aniversario.nacim.dia aniversario.nacim.mes aniversario.nacim.anyo El anidamiento de estructuras puede hacerse a ms profundidad, en cuyo caso el acceso a los campos de la variable se hace utilizando sucesivamente operadores punto.
Matrices de estructuras
Uno de los usos ms habituales de las estructuras son las matrices. Para declarar una matriz de una estructura se hace como para cualquier otra variable. Por ejemplo,
110
Introduccin al Lenguaje C
struct FICHA { char nombre[40]; int edad; float altura; }; ... ... struct FICHA vector[100]; declara un vector de 100 elementos llamado vector. Cada uno de los elementos vector[i], est formado por los 3 campos definidos en la estructura. Los campos del elemento i de vector son: vector[i].nombre vector[i].edad vector[i].altura Si alguno de los campos de la estructura es una matriz (como es el caso del campo nombre) puede accederse a cada elemento de la forma siguiente: vector[10].nombre[3] que se refiere al carcter 3 del campo nombre del elemento vector[10].
#include <stdio.h> #include <conio.h> struct FICHA { char nombre[40]; int edad; float altura; }; void Funcion (int); void main (void) { struct FICHA registro = { "Jos Garca", 37, 1.82 }; clrscr (); Funcion (registro.edad); } void Funcion (int n) { printf ("\nEdad: %d", n); }
Tambin se puede pasar la direccin de un campo de una estructura. Un programa similar al anterior, pero manejando la direccin del campo, es el siguiente:
1. Introduccin
111
#include <stdio.h> #include <conio.h> struct FICHA { char nombre[40]; int edad; float altura; }; void Funcion (int *); void main (void) { struct FICHA registro = { "Jos Garca", 37, 1.82 }; clrscr (); Funcion (®istro.edad); } void Funcion (int *n) { printf ("\nEdad: %d", *n); }
Por ltimo, tambin podemos pasar la estructura completa como argumento de una funcin. El siguiente ejemplo muestra cmo hacerlo:
#include <stdio.h> #include <conio.h> struct FICHA { char nombre[40]; int edad; float altura; }; void Funcion (struct FICHA); void main (void) { struct FICHA registro = { "Jos Garca", 37, 1.82 }; clrscr (); Funcion (registro); } void Funcion (struct FICHA n) { printf ("\nFuncion3"); printf ("\nNombre: %s", n.nombre); printf ("\nEdad: %d", n.edad); printf ("\nAltura: %f", n.altura); }
112
Introduccin al Lenguaje C
Punteros a estructuras
Cuando las estructuras son complejas el paso de la estructura completa a una funcin puede ralentizar los programas debido a la necesidad de introducir y sacar cada vez todos los elementos de la estructura en la pila. Es por ello por lo que es conveniente recurrir a pasar slo la direccin de la estructura, utilizando punteros. Un puntero a una estructura se declara del mismo modo que un puntero a cualquier otro tipo de variable, mediante el operador *. Por ejemplo, la sentencia struct MIESTR *p, var; declara un puntero p a una estructura MIESTR y una variable var con esa estructura. Despus de una declaracin como la anterior, puede hacerse una asignacin como la siguiente: p = &var; que asigna a p la direccin de la variable var. Para acceder a los campos de la estructura mediante el puntero se hace (*p).campo Sin embargo esta sintaxis es antigua y est fuera de uso. En su lugar se utiliza el operador flecha (->), formado por el guin (-) y el smbolo mayor que (>). p -> campo El siguiente programa muestra en pantalla los campos de una estructura a travs de un puntero que contiene su direccin.
#include <stdio.h> #include <conio.h> struct FICHA { char nombre[40]; int edad; float altura; }; void main (void) { struct FICHA *p, registro = { "Jos Garca", 37, 1.82 }; clrscr (); p = ®istro; printf ("\nNombre: %s", p -> nombre); printf ("\nEdad: %d", p -> edad); printf ("\nAltura: %f", p -> altura); }
Campos de bits
Los campos de bits son un caso particular de estructuras que permiten acceder a los bits individuales de una palabra. La sintaxis que define un campo de bits es la siguiente: struct nombre_estructura {
1. Introduccin
113
tipo variable1: ancho1; tipo variable2: ancho2; ... ... tipo variableN:anchoN; }; donde tipo es uno de los tipos char, unsigned char, int o unsigned int, y anchoN es un valor de 0 a 16, que representa el ancho en bits del campo de bits variableN. Si no se pone nombre al campo de bits, los bits especificados en ancho se reservan, pero no son accesibles. El siguiente ejemplo muestra una declaracin de campo de bits: struct MIESTR { int i: 2; unsigned j: 5; int : 4; int k: 1; unsigned m: 4; }; struct MIESTR var; Esta declaracin se corresponde con el siguiente diseo de la palabra:
15
14
13
12
11
10
no usados
Los campos de bits son tiles para almacenar variables lgicas en un byte, para codificar los bits de un dispositivo, etc. El acceso a los campos de bits se realiza del mismo modo que a los campos de cualquier estructura. Tan slo hay que tener en cuenta un par de restricciones con los campos de bits: no se permiten matrices y no se puede trabajar con direcciones. Los campos de bits pueden presentar algn problema respecto de la portabilidad de los programas de una mquina a otra. Aqu se ha supuesto que los campos funcionan de izquierda a derecha, pero eso puede cambiar en otras mquinas, con lo que la interprestacin de los diferente campos cambia.
Uniones
Una unin es una agrupacin de variables que ocupan la misma posicin de memoria. Se declaran de modo similar a las estructuras: union nombre_unin { tipo variable_1; tipo variable_2; ... ...
114
Introduccin al Lenguaje C
tipo variable_N; }; Cuando se declara una unin, las variables variable_1, variable_2, ..., variable_N ocupan la misma posicin de memoria. El compilador reserva el tamao suficiente para almacenar la variable ms grande. Es importante advertir una diferencia notable entre las estructuras (struct) y las uniones (union). En las primeras cada campo ocupa una posicin de memoria propia, y cuando se almacena un valor en uno de los campos, no influyen en el resto. Por el contrario, en las uniones todas las variables de la unin tienen asignada la misma posicin de memoria, lo cual implica que cuando se almacena un dato en una de ellas, influye en todas las dems. Aclaremos esto con un ejemplo sencillo. Sea la estructura struct MIESTR { char a; int b; float c; }; struct MIESTR mivar; Esta declaracin asigna 7 bytes de memoria para la variable mivar: uno para el campo a, dos ms para el campo b, y otros cuatro para el campo c.
mivar 6 5 4 3 2 1 0
Si hacemos una asignacin como mivar.b = 120; no afecta a mivar.a ni a mivar.c, pues cada uno de ellos tiene asignadas direcciones distintas de memoria. Sin embargo, sea la union union MIUNION { char a; int b; float c; }; union MIUNION mivar; Esta declaracin asigna 4 bytes de memoria para mivar (el campo ms grande de la unin es c, de 4 bytes).
1. Introduccin
115
mivar 3 2 1 0
a b c
Si ahora hacemos una asignacin como mivar.b = 120; afecta tanto a mivar.a como a mivar.c. Veamos un ejemplo. Las siguientes declaraciones utilizan las estructuras y uniones para almacenar datos de 100 alumnos y profesores de un Centro de Enseanza. Se almacena el tipo (A: alumno, P: profesor), nombre, edad, direccin y telfono. Si es alumno se almacena, adems, el grupo al que pertenece, el nmero de asignaturas que cursa y si es o no repetidor. Si es profesor se almacena el nmero de registro y el cargo que desempea.
116
Introduccin al Lenguaje C
struct ALUMNO { char grupo[15]; int asignat; char repite; }; struct PROFESOR { char nrp[16]; char cargo[21]; }; union AL_PR { struct ALUMNO al; struct PROFESOR pr; }; struct DATOS { char tipo; char nombre[40]; int edad; char direccion[40]; char telefono[8]; union AL_PR ambos; } personal[100]; El siguiente segmento de progama muestra los datos de la matriz personal.
for (i = 0; i < 100; i++) { printf ("\nNombre: %s", personal[i].nombre); printf ("\nEdad: %d", personal[i].edad); printf ("\nDireccin: %s", personal[i].direccion); printf ("\nTelfono: %s", personal[i].telefono); if (personal[i].tipo == 'A') { printf ("\nALUMNO"); printf ("\nGrupo: %s", personal[i].ambos.al.grupo); printf ("\nN de Asignaturas: %d", personal[i].ambos.al.asignat); printf ("\nRepite: %d", personal[i].ambos.al.repite); } else { printf ("\nPROFESOR"); printf ("\nN.R.P.: %s", personal[i].ambos.pr.nrp); printf ("\nCargo: %s", personal[i].ambos.pr.cargo); } }
Combinando estructuras, uniones y campos de bits podemos definir variables de 16 bits con acceso por bit, byte o palabra. struct BITS { unsigned bit0: 1; unsigned bit1: 1; unsigned bit2: 1; unsigned bit3: 1; unsigned bit4: 1; unsigned bit5: 1; unsigned bit6: 1; unsigned bit7: 1;
1. Introduccin
117
unsigned bit8: 1; unsigned bit9: 1; unsigned bit10: 1; unsigned bit11: 1; unsigned bit12: 1; unsigned bit13: 1; unsigned bit14: 1; unsigned bit15: 1; }; struct BYTES { unsigned byte0: 8; unsigned byte1: 8; }; union PALABRA { int x; struct BYTES byte; struct BITS bit; } dato; Ahora, mediante dato podemos acceder a la palabra completa (dato.x), a uno de los dos bytes (dato.byte.byteN), o a bits individuales (dato.bit.bitN). Vamos a utilizar la funcin de la biblioteca estndar biosequip() en un programa quehace uso de esta capacidad. La funcn biosequip() devuelve una palabra con la siguiente informacin: bits 14-15 bit 13 bit 12 bits 9-11 bit 8 bits 6-7 Nmero de impresoras paralelo Impresoria serie instalada Joystick instalado Nmero de puertos COM Chip DMA instalado (0: S, 1: NO) N de disqueteras (si bit0 = 1) 00: 1 disquetera 01: 2 disqueteras 10: 3 disqueteras 11: 4 disqueteras bits 4-5 Modo inicial de vdeo 00: No usado 01: 40x25 BN, adaptador color 10: 80x25 BN, adaptador color 11: 80x25 BN, adaptador monogrfico bits 2-3 RAM en placa base 00: 16k 01: 32k 10: 48k 11: 64k bit 1 Coprocesador matemtico 80x87 instalado bit 0 Arranque desde disquete El siguiente programa presenta en pantalla la informacin proporcionada por biosequip().
#include <stdio.h> #include <conio.h> #include <bios.h> struct BIOSEQUIP { unsigned arranque: 1;
118
Introduccin al Lenguaje C
unsigned cop80x87: 1; unsigned RAM_base: 2; unsigned video: 2; unsigned discos: 2; unsigned DMA: 1; unsigned puertos: 3; unsigned juegos: 1; unsigned serie: 1; unsigned paralelo: 2; }; union EQUIPO { int palabra; struct BIOSEQUIP bits; }; void Presentar (void); void main (void) { union EQUIPO bios; bios.palabra = biosequip (); clrscr (); Presentar (); gotoxy (31, 1); if (bios.bits.cop80x87) puts ("SI"); else puts ("NO"); gotoxy (31, 2); printf ("%d Kb", (bios.bits.RAM_base + 1) * 16); gotoxy (31, 3); switch (bios.bits.video) { case 1: puts ("40x25 Blanco y Negro, Adaptador Color"); break; case 2: puts ("80x25 Blanco y Negro, Adaptador Color"); break; case 3: puts ("80x25 Blanco y Negro, Adaptador Monocromo"); } gotoxy (31, 4); if (bios.bits.arranque) printf ("%d", bios.bits.discos + 1); else puts ("0"); gotoxy (31, 5); if (bios.bits.DMA) puts ("NO"); else puts ("SI"); gotoxy (31, 6); printf ("%d", bios.bits.puertos); gotoxy (31, 7); if (bios.bits.juegos) puts ("SI"); else puts ("NO");
1. Introduccin
119
gotoxy (31, 8); printf ("%d", bios.bits.serie); gotoxy (31, 9); printf ("%d\n", bios.bits.paralelo); } void Presentar (void) { puts ("Coprocesador Matemtico ..... "); puts ("RAM en placa base ........... "); puts ("Modo inicial de vdeo ....... "); puts ("N unidades de disquete ..... "); puts ("Chip DMA .................... "); puts ("N de puertos COM ........... "); puts ("Joystick .................... "); puts ("Impresora serie ............. "); puts ("Impresoras paralelo ......... "); }
Enumeraciones
Son grupos de constantes agrupadas de modo similar a las estructuras y que permiten controlar el rango de una variable. La sintaxis que permite definir una enumeracin es: enum nombre_enumeracin { constante1; constante2; ... ... constanteN; } variable; siendo nombre_enumeracin el identificador que da nombre a la enumeracin, y variable la variable que puede tomar los valores definidos para constante1, constante2, ..., constanteN. Estas constantes asumen los valores 0, 1, 2, ..., N. Por ejemplo: enum COLORES { NEGRO; AZUL; VERDE; CYAN; ROJO; MORADO; AMARILLO; BLANCO; } color; Para una enumeracin como la anterior, la sentencia printf ("%d %d", AZUL, MORADO); mostrara en pantalla los valores 1 y 5. Tambin son posibles sentencias del tipo
120
Introduccin al Lenguaje C
switch (color) { case NEGRO: ... break; case AZUL: ... ... default: ... } Es importante no olvidar que los valores definidos con los identificadores NEGRO, AZUL, etctera, no son variables sino constantes. Es posible modificar el rango de valores de una enumeracin, inicializando las constantes en el momento de la declaracin. As, la enumeracin enum VALORES { CTE1; CTE2; CTE3 = 25; CTE4; CTE5 = 31; }; define las constantes de la enumeracin con los siguientes valores CTE1: 0 CTE2: 1 CTE3: 25 CTE4: 26 CTE5: 31 ...
1. Introduccin
121
Ejercicios
1.
Deseamos hacer un listado de precios de los artculos de la empresa QUIEBRA,S.A. Los datos a procesar para cada artculo son los siguientes: Cdigo del artculo: Cadena de 3 caracteres. Descripcin: Cadena de 40 caracteres. Componentes: Matriz de 5 elementos. Cada elemento de esta matriz contendr los siguientes campos: 1. Cdigo del componente: Cadena de 8 caracteres. 2. Cantidad: Entero entre 1 y 100. 3. Precio unitario: Entero entre 500 y 5000. Construye un programa que realice las siguientes tareas: a) Capturar por teclado los datos de los artculos, almacenndolos en una matriz (mximo, 25 artculos). b) Imprimir los datos de la matriz con el formato siguiente:
Pgina:XXX COMPONENTES Cant Precio xxxxx xxxxxx xxxxx xxxxxx xxxxx xxxxxx xxxxx xxxxxx xxxxx xxxxxx Total: xxxxx xxxxx xxxxx xxxxx xxxxx xxxxxx xxxxxx xxxxxx xxxxxx xxxxxx Total: ... ... ... ...
Importe xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx xxxxxxx ... ... ... ...
xxx
xxxxxxxxxxxxxxxxxxxxxxxxx
2.
La funcin #2 de la INT 17h devuelve el estado de un puerto paralelo especificado con el registro DX. Los requerimientos previos de la funcin son: AH = 2 DX = N del puerto paralelo 0 - LPT1: 1 - LPT2: ...
122
Introduccin al Lenguaje C
... La funcin devuelve en AH el estado del puerto. Los bits de AH deben interpretarse como sigue: bit 0 bits 1-2 bit3 bit4 bit5 bit6 bit7 La impresora no respondi en el tiempo previsto No usados Error de E/S Impresora seleccionada Sin papel Reconocimiento de impresora (Acuse de recibo ACK) Impresora libre
Construye un programa que muestre esta informacin para el primer puerto paralelo (LPT1:).
1. Introduccin
123
124
Introduccin al Lenguaje C
Bsicamente, la forma de situar en la memoria datos y cdigo de un programa atiende al siguiente esquema:
Direcciones altas
Direcciones bajas
El tamao para las zonas de cdigo y de datos de vida global permanece constante durante todo el programa. La pila crece hacia abajo y en ella se almacenan las variables de mbito local utilizadas por las funciones. El montn, tambin denominado zona de almacenamiento libre, crece hacia arriba, a medida que se asigna memoria dinmicamente a peticin del programa. En algunos casos la pila puede solapar parte del montn. Tambin puede ocurrir que el montn no pueda satisfacer todas las demandas de memoria del programa. Estos problemas pueden controlarse con las funciones de asignacin dinmica de C.
1. Introduccin
125
Aunque el ejemplo anterior es correcto, se recomienda hacer la llamada a malloc() de la forma siguiente: int *bloque; ... ... bloque = (int *) malloc (100 * sizeof (int)); if (!bloque) { puts ("Memoria insuficiente"); Funcion_error (); } pues asegura la transportabilidad del cdigo. La funcin free() tiene como prototipo void free (void *bloque); y precisa la inclusin del archivo de cabecera alloc.h. Esta funcin devuelve al montn el bloque de memoria apuntado por bloque, siendo bloque un puntero proporcionado por una llamada previa a malloc(). La memoria devuelta por free() puede volver a ser asignada por otra llamada a malloc(). El programa de la pgina siguiente ilustra el uso de ambas funciones. En l se solicita memoria para almacenar una cadena de caracteres de longitud variable. Slo se usar la memoria necesaria dada por la longitud de la cadena. #include <stdio.h> #include <conio.h> #include <alloc.h> #include <stdlib.h> #include <process.h> void main (void) { char *cadena; int N; clrscr (); puts ("EJEMPLO DE ASIGNACIN DINMICA DE MEMORIA"); printf ("Longitud de la cadena: "); scanf ("%d", &N); getchar (); //Elimina el Intro de la entrada cadena = (char *) malloc (N + 1); if (!cadena) { puts ("\nMEMORIA INSUFICIENTE"); exit (1); } else printf ("\nDireccin asignada: %p", cadena); printf ("\nTeclea %d caracteres: ", N); gets (cadena); printf ("\nHas tecleado: %s", cadena); free (cadena); } //N + 1 para incluir el nulo final
126
Introduccin al Lenguaje C
Otras funciones de asignacin dinmica de Turbo C son: allocmem(), calloc(), coreleft(), farcalloc(), farmalloc() y realloc().
#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <alloc.h> #include <process.h> void main (void) { int *magico, *p; int i, j, ant_i, ant_j, orden, tope; register int valor; char N[3]; clrscr (); puts ("CUADRADO MGICO DE ORDEN N IMPAR (3 a 19)"); do { do { printf ("\nValor de N: "); Intro (wherey (), wherex (), 2, N); } while (!Strdigit (N)); orden = atoi (N); } while (orden < 3 || orden > 19 || !(orden % 2)); tope = orden * orden; magico = (int *) malloc (tope * sizeof (int)); if (!magico) { puts ("\nMemoria insuficiente"); exit (1); } for (i = 0; i < orden; i++) { for (j = 0; j < orden; j++) {
La compilacin y enlazado independientes del entorno integrado de programacin de Turbo C se estudian en el Captulo 12.
10
1. Introduccin
127
p = magico + i * orden + j; magico[i][j] *p = 0; } } i = 0; j = orden / 2; for (valor = 1; valor <= tope; valor++) { p = magico + i * orden + j; magico[i][j] if (*p) { i = ant_i + 1; j = ant_j; if (i > orden - 1) i = 0; } p = magico + i * orden + j; magico[i][j] *p = valor; ant_i = i--; ant_j = j++; if (i < 0) i = orden - 1; if (j > orden - 1) j = 0; } clrscr (); for (i = 0; i < orden; i++) { for (j = 0; j < orden; j++) { p = magico + i * orden + j; magico[i][j] printf ("%3d ", *p); } printf ("\n"); } free (magico); }
//Direccin de
//Direccin de
//Direccin de
//Direccin de
Colas dinmicas
Una cola es una estructura FIFO (First In, First Out), es decir, el primer elemento que entra en la estructura es el primero en salir. Las colas se ajustan muy bien al tipo de estructuras susceptibles de programarse dinmicamente, pues son estructuras que crecen y se contraen a medida que la informacin entra y sale de ellas. Para programar dinmicamente este tipo de estructuras (como las pilas, los rboles binarios, etc.) es necesario recurrir a las estructuras autoreferenciadas. Una estructura autoreferenciada se define, bsicamente, de la siguiente forma: struct nombre_estructura { campo_de_nformacin1; campo_de_informacin2; ... ...
128
Introduccin al Lenguaje C
campo_de_informacinN; struct nombre_estructura enlace1; struct nombre_estructura enlace2; ... ... struct nombre_estructura enlaceN; }; es decir, hay uno o varios campos en los que se almacena la informacin que se desee, y uno o varios campos de enlace con otros elementos. Para el caso de una cola dinmica de nmeros enteros cada elemento debe enlazar con el siguiente mediante una estructura autoreferenciada como la siguiente: struct COLA { int dato; struct COLA *enlace; }; siendo dato el nmero entero que se almacena en la cola, y enlace un puntero a la estructura del siguiente elemento de la cola. El siguiente programa gestiona una cola dinmica de nmeros enteros positivos. El funcionamiento del programa se basa en un men de opciones y en dos funciones, Introducir() y Sacar(), que controlan la entrada/salida de los datos en la cola. La funcin Introducir()realiza las siguientes operaciones: Llama a malloc() para solicitar memoria para el nuevo elemento. En caso de que no haya memoria disponible, la funcin devuelve un valor 0. Almacena el dato en la direccin asignada mediante malloc(). Recorre toda la cola desde el principio para localizar el ltimo elemento, a partir del cual debe incluirse el nuevo. Enlaza el elemento que antes era el ltimo con el nuevo elemento introducido en la cola. Devuelve un valor 1. La funcin Sacar() efecta las siguientes operaciones: Comprueba si la cola est vaca, en cuyo caso devuelve un valor -1. Obtiene la informacin del primer elemento de la cola. Hace que el segundo elemento de la cola sea el primero, apuntndolo con el puntero del elemento que se saca. Devuelve al montn mediante free() la memoria ocupada por el elemento que se saca. Devuelve el valor extrado. El programa completo se muestra a continuacin:
#include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> struct COLA { int dato; struct COLA *enlace; }; int Menu (void);
1. Introduccin
129
int Introducir (int n); int Sacar (void); struct COLA *primero; void main (void) { int valor, opcion, st; primero = 0; while ((opcion = Menu ()) != 3) { switch (opcion) { case 1: printf ("\nValor a introducir en la cola: "); scanf ("%d", &valor); if (!Introducir (valor)) { puts ("\nMemoria agotada. Pulse una tecla ..."); while (!getch ()); } break; case 2: valor = Sacar (); if (valor < 0) puts ("\nCola vaca"); else printf ("\nValor obtenido: %d", valor); printf ("\nPulse una tecla ..."); while (!getch ()); } } } int Menu (void) { int n; clrscr (); puts ("COLA DINMICA\n"); puts ("1. Introducir"); puts ("2. Sacar"); puts ("3. Salir\n"); do { n = getch (); } while (n < '1' || n > '3'); return n - 48; } int Introducir (int n) { struct COLA *nuevo, *actual, *anterior; nuevo = (struct COLA *) malloc (sizeof (struct COLA)); if (!nuevo) return 0; nuevo -> dato = n; nuevo -> enlace = 0; if (!primero) primero = nuevo; else { actual = primero; while (actual) {
130
Introduccin al Lenguaje C
anterior = actual; actual = anterior -> enlace; } anterior -> enlace = nuevo; } return 1; } int Sacar (void) { struct COLA *antprimero; int n; if (!primero) return -1; else { antprimero = primero; n = primero -> dato; primero = primero -> enlace; free (antprimero); return n; } }
Ejercicios
1.
Construye un programa que gestione una pila de enteros con asignacin dinmica de memoria. El programa presentar un men con las opciones 1. Introducir 2. Sacar 3. Salir El programa dispondr de funciones Introducir() y Sacar() similares a las utilizadas en el apartado "Colas dinmicas" del captulo.
1. Introduccin
131
132
Introduccin al Lenguaje C
Para poder manejar un fichero es necesario asociarle un canal. Esto se consigue mediante una operacin de apertura que se realiza mediante la funcin de biblioteca fopen(), que devuelve un puntero al canal que se asocia. El prototipo de esta funcin est definido en stdio.h y es el siguiente: FILE *fopen (const char *nombre, const char *modo); Esta funcin abre el fichero nombre y le asocia un canal mediante el puntero a FILE que devuelve. El tipo de dato FILE est definido tambin en stdio.h y es una estructura que contiene informacin sobre el fichero. Consta de los siguientes campos: typedef struct { short level; buffer unsigned flags; char fd; fichero (n que lo identifica) char hold; short bsize; unsigned char *buffer; unsigned char *curp; short token; } FILE; //indicadores de control //descriptor del
//carcter de ungetc()11 //tamao del buffer //puntero al buffer //posicin en curso //se usa para control
No debe tocarse ninguno de los campos de esta estructura a menos que se sea un programador muy experto. Cualquier cambio no controlado en los valores de esas variables, posiblemente daara el archivo. Si se produce cualquier error en la apertura del fichero, fopen() devuelve un puntero nulo y el fichero queda sin canal asociado. Los valores posibles del parmetro modo se muestran en la tabla siguiente:
DESCRIPCIN Abre un fichero slo para lectura. Si el fichero no existe fopen() devuelve un puntero nulo y se genera un error. w Crea un nuevo fichero para escritura. Si ya existe un fichero con este nombre, se sobreescribe, perdindose el contenido anterior. a Abre o crea un fichero para aadir. Si el fichero existe, se abre apuntando al final del mismo. Si no existe se crea uno nuevo. r+ Abre un fichero para leer y escribir. Si el fichero no existe fopen() devuelve un puntero nulo y se genera un error. Si existe, pueden realizarse sobre l operaciones de lectura y de escritura. w+ Crea un nuevo fichero para leer y escribir. Si ya existe un fichero con este nombre, se sobreescribe, perdindose el contenido anterior. Sobre el archivo pueden realizarse operaciones de lectura y de escritura. a+ Abre o crea un fichero para leer y aadir. Si el fichero ya existe se abre apuntando al final del mismo. Si no existe se crea un fichero nuevo. Para indicar si el canal asociado al fichero es de texto o binario, se aade al modo la letra t o b, respectivamente. As, si modo es rt, se est abriendo el fichero en modo texto slo para lectura,
MODO r
Esta funcin tiene como prototipo int ungetc (int carcter, FILE *canal); y devuelve carcter al canal de entrada canal, lo que permite que pueda volver a ser leido.
11
1. Introduccin
133
mientras que si modo es w+b se abrir o crear un fichero en modo binario para lectura y para escritura12. Para cerrar un fichero y liberar el canal previamente asociado con fopen(), se debe usar la funcin fclose() definida en stdio.h y cuyo prototipo es int fclose (FILE *canal); Esta funcin devuelve 0 si la operacin de cierre ha tenido xito, y EOF en caso de error. EOF es una macro definida en stdio.h de la siguiente forma: #define EOF (-1)
El siguiente programa abre slo para lectura un fichero de nombre DATOS.DAT y, posteriormente lo cierra.
#include <stdio.h> #include <process.h> void main (void) { FILE *f; int st;
//Para exit()
f = fopen ("DATOS.DAT", "rt"); if (!f) { puts ("Error al abrir el fichero"); exit (1); } //Aqu vendran las operaciones sobre el fichero st = fclose (f); if (st) puts ("Error al cerrar el fichero"); }
Es habitual escribir la sentencia de apertura del fichero como sigue: if (!(f = fopen ("DATOS.DAT", "rt"))) { puts ("Error al abrir el fichero"); exit (1); }
134
Introduccin al Lenguaje C
int ferror (FILE *canal); Esta funcin devuelve 0 si la ltima operacin sobre el fichero se ha realizado con xito. Hay que tener en cuenta que todas las operaciones sobre el fichero afectan a la condicin de error, por lo que debe hacerse el control de error inmediatamente despus de cada operacin o, de lo contrario, la condicin de error puede perderse. La forma de invocar esta funcin puede ser if (ferror (f)) puts ("Error de acceso al fichero"); siendo f un puntero a FILE. Se puede conseguir un mensaje asociado al ltimo error producido mediante la funcin perror() cuyo prototipo, definido en stdio.h, es void perror (const char *cadena); Esta funcin enva a stderr (generalmente la pantalla) la cadena indicada en el argumento, dos puntos y, a continuacin, un mensaje del sistema asociado al ltimo error producido. La forma en que se relaciona el error con el mensaje es mediante una variable global predefinida llamada errno (definida en errno.h) que se activa cuando se producen errores. Por ejemplo, dado el segmento de programa FILE *f; if (!(f = fopen ("DATOS.DAT", "rb"))) { printf ("\nError %d. ", errno); perror ("DATOS.DAT"); exit (1); } si no existe el fichero DATOS.DAT se enva a pantalla el mensaje Error 2. DATOS.DAT: No such file or directory Cada vez que se realiza una operacin de lectura sobre un fichero, el indicador de posicin del fichero se actualiza. Es necesario, pues, controlar la condicin de fin de fichero. Por ello, debemos saber que cuando se intentan realizar lecturas ms all del fin de fichero, el carcter ledo es siempre EOF. Sin embargo en los canales binarios un dato puede tener el valor EOF sin ser la marca de fin de fichero. Es aconsejable, por ello, examinar la condicin de fin de fichero mediante la funcin feof(), cuyo prototipo, definido en stdio.h, es int feof (FILE *canal); Esta funcin devuelve un valor diferente de cero cuando se detecta el fin de fichero. Un algoritmo que lea todo un archivo puede ser el siguiente:
#include <stdio.h> #include <process.h> void main (void) { FILE *f; if (!(f = fopen ("DATOS.DAT", "rt"))) {
1. Introduccin
135
perror ("\nDATOS.DAT"); exit (1); } //operacin de lectura sobre DATOS.DAT while (!feof (f)) { //tratamiento //operacin de lectura sobre DATOS.DAT } fclose (f); if (ferror (f)) puts ("Error al cerrar el archivo DATOS.DAT"); }
E/S de caracteres
Para leer caracteres individuales de un fichero se utiliza la funcin int fgetc (FILE *canal); o su macro equivalente int getc (FILE *canal); Ambas estn definidas en stdio.h y son completamente idnticas. Devuelven el carcter ledo e incrementan el contador de posicin del fichero en 1 byte. Si se detecta la condicin de fin de fichero, devuelven EOF, pero para canales binarios es mejor examinar dicha condicin mediante feof(). Para escribir caracteres individuales en un fichero se utiliza la funcin int fputc (int carcter, FILE *canal); o su macro equivalente int putc (int carcter, FILE *canal); Ambas tienen el prototipo definido en stdio.h y son completamente idnticas. Escriben el carcter indicado en el argumento (que puede ser tambin una variable char) en el fichero asociado a canal. Si no hay error, devuelven el carcter escrito; en caso contrario devuelven EOF. El siguiente programa copia carcter a carcter el fichero DATOS.DAT en COPIA.DAT.
#include <stdio.h> #include <process.h> void main (void) { FILE *fent, *fsal; char caracter; if (!(fent = fopen ("DATOS.DAT", "rb"))) { perror ("DATOS.DAT"); exit (1); }
136
Introduccin al Lenguaje C
if (!(fsal = fopen ("COPIA.DAT", "wb"))) { perror ("COPIA.DAT"); exit (1); } caracter = getc (fent); while (!feof (fent)) { putc (caracter, fsal); if (ferror (fsal)) puts ("No se ha escrito el carcter"); caracter = getc (fent); } fclose (fent); fclose (fsal); }
La misma operacin puede realizarse en una sola lnea mediante while (!feof (fent)) putc (getc (fent), fsal); Existen dos funciones anlogas a getc() y putc() pero para leer y escribir enteros en lugar de caracteres: int getw (FILE *canal); int putw (int entero, FILE *canal); Ambas estn definidas en stdio.h y la nica diferencia con getc() y putc() est en que se procesan dos bytes en cada operacin. Estos dos bytes deben interpretarse como un nmero entero. La funcin getw() no debe usarse con ficheros abiertos en modo texto.
#include <stdio.h> #include <process.h> void main (void) { FILE *f; register int i = 1;
1. Introduccin
137
char cadena[81]; if (!(f = fopen ("AUTOEXEC.BAT", "rt"))) { perror ("AUTOEXEC.BAT"); exit (1); } fgets (cadena, 80, f); while (!feof (f)) { printf ("%d: %s", i, cadena); i++; fgets (cadena, 80, f); } fclose (f); }
Para escribir cadenas de caracteres se utiliza la funcin int fputs (const char *cadena, FILE *canal); cuyo prototipo est definido en stdio.h, y que escribe la cadena indicada en el argumento en el fichero asociado a canal. Hay que tener en cuenta que fputs() no copia el carcter nulo ni aade un carcter nueva lnea al final. Si no se produce error, fputs() devuelve el ltimo carcter escrito; en caso contrario devuelve EOF. El siguiente programa escribe en la impresora las cadenas de caracteres que se van tecleando, hasta que se pulsa .
#include <stdio.h> #include <string.h> void main (void) { char *cadena[85]; printf ("\nTeclee cadena: "); gets (cadena); while (cadena[0]) { strcat (cadena, "\n\r"); fputs (cadena, stdprn); gets (cadena); } }
138
Introduccin al Lenguaje C
La funcin fread() lee contador bloques de datos del fichero asociado a canal y los sita en buffer. Cada bloque contiene nbytes bytes. La funcin fwrite() vuelca en el fichero asociado a canal los contador bloques de nbytes bytes que se encuentran almacenados a partir de buffer. Si la operacin tiene xito, fread() devuelve el nmero de bloques (no de bytes) realmente ledos. Anlogamente, fwrite() devuelve el nmero de bloques realmente escritos. La declaracin void *buffer indica que buffer es un puntero a cualquier tipo de variables. En el programa siguiente se almacenan en el fichero MATRIZ.FLO un conjunto de 10 nmeros de tipo float. La escritura se hace con una sentencia fwrite() para cada dato. En la operacin de lectura se leen los 10 datos de una sola vez con una sentencia fread() y, a continuacin, se muestran en pantalla.
#include <stdio.h> #include <process.h> #include <conio.h> void main (void) { register int i; FILE *f; float elem, matriz[10]; if (!(f = fopen ("MATRIZ.FLO", "w+b"))) { perror ("MATRIZ.FLO"); exit (1); } for (i = 0; i <= 9; i++) { printf ("\nTeclee nmero: "); scanf ("%f", &elem); fwrite (&elem, sizeof (elem), 1, f); } rewind (f); fread (matriz, sizeof (matriz), 1, f); clrscr (); for (i = 0; i <= 9; i++) printf ("\n%d: %f", i, matriz[i]); fclose (f); }
Fijmonos en algunos detalles del programa anterior. La sentencia fwrite (&elem, sizeof (elem), 1, f); vuelca en el fichero, cada vez, los 4 bytes -sizeof (elem)- de elem. Una vez finalizado el primero de los bucles for se han ejecutado 10 operaciones fwrite() y, por tanto, el indicador de posicin del fichero apunta al final del mismo. Para poder realizar la lectura de los datos hemos de recolocar este indicador al principio del fichero. Esto puede hacerse de dos formas: Cerrando el fichero y volvindolo a abrir para lectura. Mediante la funcin rewind().
1. Introduccin
139
En el ejemplo se utiliza el segundo mtodo. La funcin rewind() reposiciona el indicador de posicin del fichero al principio del mismo. Su prototipo, definido en stdio.h, es el siguiente: void rewind (FILE *canal); Siguiendo con el programa anterior, la sentencia fread (matriz, sizeof (matriz), 1, f); lee del fichero 40 bytes -sizeof (matriz)- y los sita en matriz. Fijmonos que ahora no es necesario escribir &matriz en el primer parmetro, pues matriz ya es un puntero. Habitualmente fread() y fwrite() se utilizan para leer o escribir estructuras. Vemoslo con un programa que crea un archivo de listn telefnico. El registro de ese archivo constar de los siguientes campos: Nombre Domicilio Poblacin Provincia Telfono 40 caracteres 40 caracteres 25 caracteres 15 caracteres 10 caracteres
#include <stdio.h> #include <conio.h> #include <process.h> typedef struct { char nom[41]; char dom[41]; char pob[26]; char pro[16]; char tel[11]; } REG; void main (void) { FILE *f; REG var; if (!(f = fopen ("LISTIN.TEL", "wb"))) { perror ("LISTIN.TEL"); exit (1); } clrscr (); printf ("Nombre: "); gets (var.nom); while (var.nom[0]) { printf ("\nDomicilio: "); gets (var.dom); printf ("\nPoblacin: "); gets (var.pob); printf ("\nProvincia: "); gets (var.pro); printf ("\nTelfono: ");
140
Introduccin al Lenguaje C
gets (var.tel); fwrite (&var, sizeof (var), 1, f); if (ferror (f)) { puts ("No se ha almacenado la informacin"); getch (); } clrscr (); printf ("Nombre: "); gets (var.nom); } fclose (f); }
El siguiente programa ilustra como se pueden leer bloques de registros de un fichero. Concretamente lee los registros del fichero LISTIN.TEL en grupos de 4, mostrando en pantalla los campos Nombre y Telfono.
#include <stdio.h> #include <conio.h> #include <process.h> typedef struct { char nom[41]; char dom[41]; char pob[26]; char pro[16]; char tel[11]; } REG; void main (void) { FILE *f; REG var[4]; int i, n; if (!(f = fopen ("LISTIN.TEL", "rb"))) { perror ("LISTIN.TEL"); exit (1); } do { clrscr (); n = fread (var, sizeof (REG), 4, f); for (i = 0; i < n; i++) printf ("\n%-41s %s", var[i].nom, var[i].tel); puts ("\nPulse una tecla ..."); getch (); } while (!feof (f)); fclose (f); } Si nos fijamos en la sentencia fread() de este programa, se leen en cada operacin 4 bloques de sizeof (REG) bytes (135, tamao de cada registro). La misma cantidad de bytes se leera mediante la sentencia
1. Introduccin
141
fread (var, sizeof (var), 1, f); sin embargo, as no tendramos un control adecuado sobre la variable n.
Acceso directo
El acceso directo (tanto en lectura como en escritura) a un archivo, se realiza con la ayuda de la funcin fseek() que permite situar el indicador de posicin del archivo en cualquier lugar del mismo. El prototipo de esta funcin es: int fseek (FILE *canal, long nbytes, int origen); Esta funcin sita el indicador de posicin del fichero nbytes contados a partir de origen. Los valores posibles del parmetro origen y sus macros asociadas se muestran en la siguiente tabla: Error! Marcador no definido.ORIGEN Principio del fichero Posicin actual Fin del fichero VALOR MACRO
0 1 2
La funcin devuelve 0 cuando ha tenido xito. En caso contrario devuelve un valor diferente de 0.
142
Introduccin al Lenguaje C
Esta funcin simplemente maneja el indicador de posicin del fichero, pero no realiza ninguna operacin de lectura o escritura. Por ello, despus de usar fseek() debe ejecutarse una funcin de lectura o escritura. El siguiente programa crea un archivo llamado FRASE.TXT con una cadena de caracteres. Posteriormente lee un carcter de la cadena cuya posicin se teclea.
#include <stdio.h> #include <conio.h> #include <string.h> #include <process.h> void main (void) { FILE *f; int nbyte, st; char frase[80], caracter; if (!(f = fopen ("FRASE.TXT", "w+t"))) { perror ("FRASE.TXT"); exit (1); } clrscr (); printf ("Teclee frase: "); gets (frase); fwrite (frase, strlen (frase) + 1, 1, f); printf ("\nLeer carcter n: "); scanf ("%d", &nbyte); st = fseek (f, (long) nbyte, SEEK_SET); if (st) puts ("Error de posicionamiento"); else { caracter = getc (f); if (caracter != EOF) printf ("\nEl carcter es: %c", caracter); else puts ("Se sobrepas el fin de fichero"); } fclose (f); }
Puede usarse fseek() para acceder a registros. Para ello debe calcularse previamente en qu byte del fichero comienza el registro buscado. El siguiente programa escribe registros ayudndose de fseek().
#include <stdio.h> #include <conio.h> #include <process.h> typedef struct { char nombre[40]; int edad; float altura; } REGISTRO;
1. Introduccin
143
void main (void) { FILE *f1; REGISTRO mireg; int num; long int puntero; if (!(f1 = fopen ("REGISTRO.DAT", "r+b"))) { puts ("Error de apertura"); exit (1); } clrscr (); printf ("Escribir registro n: "); scanf ("%d", &num); while (num > 0) { getchar (); printf ("Nombre: "); gets (mireg.nombre); printf ("Edad: "); scanf ("%d", &mireg.edad); printf ("Altura: "); scanf ("%f", &mireg.altura); puntero = (num - 1) * sizeof (REGISTRO); if (fseek (f1, puntero, SEEK_SET)) puts ("Error de posicionamiento"); else { fwrite (&mireg, sizeof (mireg), 1, f1); if (ferror (f1)) { puts ("ERROR de escritura"); getch (); } } clrscr (); printf ("Escribir registro n: "); scanf ("%d", &num); } fclose (f1); }
144
Introduccin al Lenguaje C
Ejercicios
1.
Escribe un programa que vaya almacenando en un archivo todos los caracteres que se tecleen hasta que se pulse CTRL-Z. El nombre del archivo se pasar como parmetro en la lnea de rdenes.
2.
Escribe un programa que lea un archivo de texto y cambie todas las apariciones de una palabra determinada, por otra. El programa se ejecutar con la orden PROG fichero palabra1 palabra2 siendo palabra1 la palabra sustituida por palabra2. El programa debe informar del nmero de sustituciones efectuadas. Nota: Se supondr que las lneas del fichero no tienen ms de 80 caracteres.
3.
En el club de baloncesto BAHEETO'S BASKET CLUB se est realizando una campaa de captacin de jugadores altos. Se dispone de un fichero con datos de aspirantes, llamado ALTOS.DAT, que se describe a continuacin ALTOS.DAT Aspirantes a jugadores del club Campo Descripcin Nombre del aspirante nom Altura del aspirante (en metros) alt Provincia de nacimiento pro Los campos de tipo char incluyen el nulo final El fichero almacena un mximo de 500 registros. La provincia se almacena en maysculas. El fichero no est ordenado. Construye un programa que realice las siguientes operaciones: Solicitar por teclado una provincia y almacenar en sendas matrices los nombres y las alturas de los aspirantes nacidos en la provincia indicada. Calcular la altura media de todos los aspirantes de dicha provincia. Emitir un informe impreso con los nombres y alturas de los aspirantes de la provincia cuya altura supere la media. El formato del listado debe ser el siguiente: Provincia: xxxxxxxxxxxxxxxxxxxxxxxxx Altura Media: x.xx Nombre Altura xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ... 50 lneas de detalle por pgina x.xx
4.
Se tienen dos archivos de datos ARTIC.DAT y NOMPRO.DAT, que se describen a continuacin: ARTIC.DAT Maestro de Artculos Campo Descripcin Cdigo del artculo cod Descripcin des Existencia exi
1. Introduccin
145
Precio de compra unsigned pco Precio de Venta unsigned pvp Cdigo del proveedor char(5) pro Los campos de tipo char incluyen el nulo final Los campos cod y pro almacenan slo dgitos numricos y estn completados a la izquierda con ceros. Por ejemplo, el artculo de cdigo 65 se almacena como 000065. El fichero est ordenado ascendentemente por el campo cod. No estn todos los valores de cod.
NOMPRO.DAT Nombres de proveedores Campo Descripcin Tipo Nombre del proveedor char(41) nom Cada nombre de proveedor est almacenado en el registro cuyo nmero coincide con su cdigo. As, el nombre del proveedor de cdigo 000041 se almacena en el registro 41. Escribe un programa que emita un informe impreso con el formato de la pgina siguiente. Al inicio del programa se solicitarn por teclado los cdigos de artculo inicial y final que limitarn los registros que se deben listar.