Академический Документы
Профессиональный Документы
Культура Документы
DE
LENGUAJE “C”
Lenguaje “C” Indice
Este manual ha sido desarrollado por el departamento técnico de DELTA PC. III
Indice Lenguaje “C”
INTRODUCCIÓN
LENGUAJE “C”
I.1.-¿QUE ES EL "C"?
El "C" es un lenguaje de programación sencillo y elegante, que se ha transformado en el
medio elegido por un número cada vez mayor de programadores, para comunicarse con su ordenador.
El "C" fue creado por Dennis Ritchie, de los Laboratorios Bell, en 1972, cuando trabajaba,
junto con Ken Thompson, en el diseño del sistema operativo UNIX. El "C" desa-rrollado por Ritchie
deriva del lenguaje B, creado por Thompson previamente.
El lenguaje "C" se está transformando rápidamente en una de las bases de programa-ción más
importantes y populares. Es un lenguaje moderno, que incorpora las características de control
apuntadas como deseables por la teoría y la práctica de la informática. Su propio diseño hace que
resulten naturales para el usuario aspectos como la planificación escalonada, programación
estructurada y diseño modular, obteniendo como resultado programas más fia-bles y comprensibles.
El "C" es un lenguaje portátil. Con ello queremos significar que los programas "C" escritos en
un sistema pueden ejecutarse en otros sistemas sin apenas modificación; modifi-caciones que se
pueden reducir a cambiar unas cuantas sentencias de entrada en un fichero de encabezamiento (header)
que acompaña al programa principal. Desde este punto de vista el "C" es el lenguaje líder en lenguajes
portátiles. Existen compiladores "C" para unos 40 sistemas, que abarcan desde microprocesadores de 8
bits hasta los actuales campeones de ve-locidad en ordenadores, los Cray.
El lenguaje "C" es poderoso y flexible. Por ejemplo, la mayor parte del sistema ope-rativo
UNIX (poderoso y flexible como pocos), está escrita en "C". Incluso están escritos en "C" los
compiladores e intérpretes de otros lenguajes, como FORTRAN, APL, PASCAL, LISP, LOGO,
BASIC, etc. Por ello, al ultilizar cualquiera de estos lenguajes, a la postre, hay un programa "C" que
está haciendo el trabajo de producción del programa ejecutable final. Se han utilizado programas "C2
para resolver problemas de física e ingeniería, e incluso para la producción de secuencias animadas en
películas como El Retorno del Jedi.
El "C" posee control sobre aspectos del ordenador asociados generalmente con lengua-jes
ensambladores.
En resumen, el "C" está destinado a ser uno de los lenguajes más importantes de esta década y
de los años venideros. Se utiliza en miniordenadores y en ordenadores personales. Lo usan compañías
de sotfware, estudiantes en general y entusiastas de todas las clases.
Hay varias razones por las que se utiliza el "C" para programación de sistemas. A menudo
estos programas deben ejecutarse muy rápidamente. Los programas generados por los compiladores
"C", suelen ejecutarse tan rápidamente como los escritos en lenguaje ensam-blador. En el pasado la
mayoría del sotfware de sistema tenía que escribirse en lenguaje en-samblador, ya que ninguno de los
lenguajes de computadora disponibles podían generar pro-gramas que fueran lo suficientemente
rápidos al ejecutarlos. El escribir en lenguaje ensam-blador es un trabajo duro y tedioso, el "C" reduce
tremendamente los costes.
Otra razón por la que el "C" es frecuentemente utilizado para la programación de sistemas es
que es un lenguaje para los programadores. Los programadores profesionales se ven atraídos hacia él
porque no les impone restricciones y les permite una fácil manipulación de bits, bytes, y de
direcciones. El programador de sistemas necesita de las facilidades del "C" para el control directo de
las funciones de E/S y de gestión de memoria. El "C" permite que un programa refleje la personalidad
del programador.
Otros usos del lenguaje "C" ya han sido mencionados en los puntos anteriores de esta
introducción.
El "C" es un lenguaje "compilado". Por ello los pasos básicos a seguir desde el mo-mento en
que se empieza a escribir el programa hasta ejecutarlo son:
CAPÍTULO 1
ELEMENTOS DEL “C”
1.1.-INTRODUCCION.
En este primer apartado describimos los elementos del lenguaje "C", es decir los nombres,
números y caracteres usados para crear un programa "C". Se describen:
1.2.-CONJUNTO DE CARACTERES.
1.2.1.-LETRAS Y DÍGITOS.
El conjunto de caracteres "C" incluyen las letras mayúsculas y minúsculas del alfabeto inglés
y los 10 dígitos decimales del sistema numérico árabe:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9
Estas letras y dígitos pueden usarse para crear constantes, identificadores y sentencias. El
lenguaje "C" diferencia entre mayúsculas y minúsculas.
1.2.2.-CARACTERES ESPACIO.
Los caracteres <Space>,<Tab> y <Return> son espacios en blanco y todos tienen el mismo
propósito de espacios entre palabras y líneas.
El compilador "C" ignora los espacios en blanco a no ser que ellos formen parte de cadena
literal. Por ello se pueden usar los caracteres espacio para hacer más legibles los programas.
Estos caracteres se usan para múltiples propósitos, por ejemplo para organizar el texto en un
programa, definir las tareas a realizar por el compilador, etc. Son los siguientes:
! │ / \ ~ _ # % & ^ * - = +
El resto de caracteres que no aparecen en esta relación solo pueden figuran como parte de las
cadenas o literales, de las constantes y comentarios.
1.2.4.-SECUENCIAS DE ESCAPE.
Las secuencias de Escape son una combinación de caracteres especiales representados como
espacios en blanco, caracteres no gráficos y caracteres constantes en las cadenas. Estas secuencias se
usan para especificar acciones comunes como pueden ser retornos de carro, movimientos de tabulado;
también se utilizan para representar caracteres que tienen significado para el C, como por ejemplo el
carácter".
Una secuencia de escape consiste en un backslash (\) seguido por una letra o combinación de
dígitos. Las secuencias de escape del lenguaje "C" son las siguientes:
Las secuencias "\ddd" y "\xdd" permite asignar un carácter ASCII, dandolo como tres dígitos
para octal y como dos dígitos para hexadecimal. Por ejemplo el carácter backspace se puede dar como
"\010" o "\x08", el carácter nulo ASCII se puede dar como "\0" or "\x0".
En una secuencia de escape octal solo pueden aparecer dígitos octales, y al menos debe figurar
uno de los dígitos. Por ejemplo el carácter backspace se puede también dar como "\10". De la misma
forma una secuencia de escape hexadecimal tiene que contener al menos un dígito, es decir el segundo
puede omitirse si es el caso. Por ejemplo la secuencia de escape para el carácter hexadecimal será
"\x8". En ambos casos si la secuencia de escape figura en una cadena es más seguro incluir todos los
dígitos.
1.2.5.-OPERADORES.
Los operadores son combinaciones especiales de caracteres que especifican como los valores
son transformados y asignados. Los operadores tienen que especificarse exactamente como aparecen
en la relación que sigue, teniendo en cuenta que los operadores multicarácter no llevar espacio en
blanco entre ellos.
- Operadores aritméticos.
+ Suma.
- Resta.
* Multiplicación.
/ División.
% Resto de división entera.
++ Incremento.
-- Decremento.
= Asignación simple.
+= Asignación de adición.
-= Asignación de sustracción.
*= Asignación de producto.
/= Asignación de división.
%= Asignación de resto.
- Operadores de relación.
- Operadores lógicos.
&& And.
|| Or.
! Negación.
- Operadores binarios.
& And.
| Or.
^ Xor.
~ Not (Complemento a uno).
>> Desplazamiento a la derecha.
<< Desplazamiento a la izquierda.
>>= Asignación de desplazamiento a derecha.
<<= Asignación de desplazamiento a izquierda.
&= Asignación de AND binario.
|= Asignación de OR binario.
^= Asignación de XOR binario.
- Operadores especiales.
1.3.-CONSTANTES.
Una constante es un número, un carácter, o una cadena de caracteres que se pueden usar como
un valor en un programa. El valor de una constante no se puede cambiar durante la ejecución del
programa.
1.3.1.-CONSTANTES ENTERAS.
Una constante entera es número decimal, octal o hexadecimal que representa un valor entero.
Una constante decimal esta formada por uno o más dígitos decimales (de 0 a 9).
0digitos_oct
Donde digitos_oct son uno o más dígitos octales (de 0 a 7). El 0 inicial del formato es
obligatorio.
0xdigitos_hex
Donde dígitos_hex son uno o más dígitos hexadecimales (de 0 a 9 y las mayúsculas o
minúsculas "a" a "f"). El 0 inicial seguido por una x es obligatorio.
No pueden aparecer caracteres en blanco entre los digitos de una constante entera.
Ejemplos:
Para crear constantes enteras negativas el signo menos (-) se coloca delante de esta, el cual es
tratado como un operador, dando lugar a una expresión de valor negativo.
A las constantes decimales se les asigna el tipo int o el tipo long dependiendo del tamaño del
valor; a las constantes octales y hexadecimales también se les asigna tipo int o long según el valor.
El programador puede forzar directamente al compilador "C" a que una constante entera tenga
tipo long añadiéndole la letra "l" o "L" al final de la constante. Ejemplos:
Una constante de punto flotante es un número decimal que representa un número real con
signo. El valor de un número real con signo incluye una parte entera, una parte fraccionaria y un
exponente. Las constantes de punto flotante tienen el siguiente formato:
[digitos][.digitos][E[-]dígitos]
Donde dígitos representa uno o más digitos decimales (0 a 9), y E (ó e) es el símbolo del
exponente. Los dígitos antes del punto decimal pueden omitirse, lo mismo para los que están después
del punto decimal; pero no ambos a la vez. El punto decimal solo se puede omitir cuando se incluye el
exponente. Entre los componentes de una constante de punto flotante no puede figurar ningún carácter
blanco.
Para crear constantes de punto flotante negativas el signo menos (-) se coloca delante de esta,
el cual es tratado como un operador, dando lugar a una expresión de valor negativo. Vemos algunos
ejemplos de constantes y expresiones en punto flotante:
CONSTANTES.
25.34 .32
4.141E3 .0092e3
6412e-3 -.324
-0.0016 -.138E-2
-6.3e-2
71E-5
1.3.3.-CONSTANTES CARÁCTER.
Ejemplos:
CONSTANTES.
------------------------
'f' Minúscula f.
'% Signo %.
'\n' Carácter nueva línea.
'\b' Retroceso.
'\'' Comilla simple.
'\\' Backslash.
1.3.4.-CONSTANTES CADENA.
Una constante cadena es una secuencia de letras, digitos y símbolos encerrados entre comillas.
Una constante cadena es tratada como un array de caracteres, es decir cada elemento del array es un
valor de carácter simple. Para usar las comillas dobles o el símbolo backslash (\) como parte de una
constante cadena se preceden estos con un carácter backslash.
Por ejemplo:
CONSTANTES.
------------------------------
"La pantalla amiga ANTENA 5."
"Pulsa una opción \n o pulsa Return"
"Primero\\Segundo"
"\"Si, es así \" dijo ella."
array es el número de caracteres en la cadena más uno, ya que el carácter nulo almacenado después del
último carácter cuenta como un elemento del array.
1.4.-IDENTIFICADORES.
Los identificadores son los nombres que se utilizan para nombrar las variables, funciones y
etiquetas en los programas. Un identificador es una secuencia de una o más letras, dígitos o guiones de
subrayado (_) que comienzan con una letra o guión de subrayado. Los identificadores pueden tener
cualquier número de caracteres pero solo lo 31 primeros caracteres son significativos para el
compilador. Los identificadores que comiencen con un guión de subrayado pueden dar lugar a
conflictos con los nombres de las rutinas ocultas del sistema y producir errores.
IDENTIFICADORES.
---------------------
i
cent
temperatura
num_de_pagina
avanzar9
El compilador "C" diferencia entre mayúsculas y minúsculas. Por ello cada uno de los
siguientes identificadores es único.
IDENTIFICADORES.
---------------------
sumar
suMAR
Sumar
1.5.-PALABRAS CLAVE.
Las palabras clave son identificadores predefinidos que tienen un significado especial para el
compilador C, por ello deben de escribirse tal como están definidas. Los nombres de los
identificadores usados por el programador no pueden coincidir con ninguna de las siguientes palabras
clave.
PALABRAS RESERVADAS
auto default float register switch
break do for return typedef
case double goto short union
char else if sizeof unsigned
const enum int static void
continue extern long struct while
Estas palabras clave no pueden ser redefinidas. Sin embargo se puede especificar un texto que
sustituya a la palabra reservada antes de la compilación, usando para ello las directivas del
preprocesador.
1.6.-COMENTARIOS.
Un comentario es una secuencia de caracteres que son tratados como carácter de espacio en
blanco por el compilador es decir es ignorado. Un comentario tiene el formato:
/* Comentario */
COMENTARIOS.
---------------------------------------------------------
/* Los comentarios se incluyen para documentar
las lineas de un programa. */
CAPÍTULO 2
ESTRUCTURA DE UN PROGRAMA “C”
2.1.-INTRODUCCION.
Este capítulo describe la estructura de un programa fuente en lenguaje "C". En él se habla de
una serie de conceptos que se desarrollarán más a fondo en los sucesivos capítulos, como son las
directivas del procesador, las funciones, etc.
Estas dos funciones, printf() y scanf() funcionan de la misma forma, utilizando cada una de
ellas una "tira de caracteres de control" y una lista de "argumentos".
2.2.1.-UTILIZACIÓN DE PRINTF().
Formato:
printf(Control,campo1,camplo2,......);
Campo1, campo2,etc., son las distintas variables o constantes a imprimir. Pueden ser también
expresiones, las cuales se evalúan antes de imprimir el resultado. Control es una tira de caracteres que
describe la manera en que han de imprimirse los campos. Por ejemplo:
En este ejemplo control es la frase entre comillas y número y ron son los campos.
Las instrucciones que se han de dar a printf() cuando se desea imprimir una variable dependen
del tipo de variable de que se trate. Así tendremos que utilizar al notación %d para imprimir un entero,
y %c para imprimir un carácter. Si se desea imprimir el % como símbolo hay que indicarlo
duplicándolo. Ejemplo:
pc=2*6;
printf("Un %d%% del beneficio era ficticio. \n",pc);
En la siguiente tabla se muestran los identificadores y los tipos que imprimen utilizados por la
función printf().
Identificador Salida
-------------- ------
%d Entero decimal.
%c Carácter.
%s Tira de caracteres.
%e Número de punto flotante
(Notación exponencial).
%f Número de punto flotante
(Notación decimal).
Identificador Salida
-------------- ------
%g Usa %f ó %e, según el caso.
%u Entero decimal sin signo.
%o Entero octal sin signo.
%x Entero hexadecimal sin signo.
/* -------------------
Programa: printf1.c
------------------- */
# define ELOGIO " ! Por Júpiter, que tío ! "
main()
{
char nombre[50];
printf("\n\n Como te llamas ? ");
scanf("%s",nombre);
printf("\n Hola, %s. %s \n\n",nombre,ELOGIO);
}
/* -------------------
Programa: printf2.c
------------------- */
#define PI 3.14159
main()
{
float área, circun, radio;
printf("Cual es el radio de su tortilla ? ");
scanf("%f", &radio);
área = PI * radio * radio;
circun = 2.0 * PI * radio;
printf("\n");
printf("Los parametros basicos de su tortilla son: \n");
printf(" circunferencia =%1.2f \n área = %1.2f \n",
circun, área);
}
/* -------------------
Programa: printf3.c
------------------- */
#define PI 3.14159
main()
{
int numero = 5;
float ron = 13.5;
int coste = 3100;
printf("\n");
printf(" Las %d mujeres se bebieron %f vasos de ron. \n",
numero,ron);
printf(" El valor de pi es %f. \n",PI);
printf(" Una letra %c de coste %d \n",'h',coste);
}
Los modificadores son apéndices que se agregan a los especificadores de conversión básicos
para modificar la salida. Se colocan entre el símbolo % y el carácter que define el tipo de conversión.
Los símbolos a emplear son:
Modificador Significado.
----------- ---------------
- El campo correspondiente se comenzará a escribir en el extremo izquierdo.
número Anchura mínima del campo. Si la cantidad a imprimir no entra en el lugar
asignado, se usa automáticamente un campo mayor.
.número Precisión. Es el número de decimales en los tipos flotantes.
l Indica tipo long en vez de int. Ej. %ld.
/* -------------------
Programa: printf4.c
------------------- */
main()
{
printf("\n");
printf(" <%d> \n",336);
printf(" <%o> \n",336);
printf(" <%x> \n",336);
printf(" <%d> \n",-336);
printf(" <%u> \n",-336);
printf("\n");
}
2.2.4.-USO DE SCANF()
Scanf() al igual que printf() emplea una tira de caracteres de control y una lista de argumentos.
Sin embargo la función scanf() emplea en sus listas punteros a variables. Con esta función se siguen
las dos siguientes reglas:
1. Si se desea leer un valor perteneciente a cualquiera de los tipos básicos, se coloca el nombre
de la variable precedido por un &.
2. Si lo que se desea es leer una variable de tipo string no se usa el símbolo &.
/* ------------------
Programa: scanf1.c
------------------ */
main()
{
int edad;
float sueldo;
char cachorro[30];
printf("Confiese su edad, sueldo y mascota favorita.\n");
scanf("%d %f",&edad,&sueldo);
scanf("%s",cachorro);
printf("\nTienes %d anos. ganas %.0f pts. \n", edad,sueldo;
printf("y tu mascota es (el/la) : %s\n\n",cachorro);
}
Scanf() considera que dos campos de entrada son diferentes cuando están separados por
blancos o tabulados. Va encajando cada especificador de conversión en su campo correspondiente,
ignorando los blancos intermedios.
2. Las opciones %f y %e son equivalentes. Ambas aceptan un signo opcional, una tira de dígitos
con o sin punto decimal y un campo para el exponente, también opcional.
2.3.-PROGRAMA FUENTE.
Un programa fuente en lenguaje "C" es una colección de una o más directivas, declaraciones
y/o definiciones.
Las sentencias directivas obligan al preprocesador a repetir una serie de acciones especificas
en el texto del programa con anterioridad a la compilación.
Las declaraciones establecen los nombres y atributos de las variables, las funciones y los
tipos usados en el programa.
Las definiciones son declaraciones que describen variables y funciones. Una definición de una
variable da un valor inicial a la variable declarada conjuntamente con su nombre y tipo. La definición
de funciones consiste en definir el cuerpo de la función, le da un nombre, los parámetros formales y el
retorno de un tipo.
Vemos un ejemplo ilustrativo de un programa fuente C.
/* Definición de variables */
int x=1;
int y=2;
/* Declaración de una función */
extern int printf(char *,);
main()
/* Definición de función
para la función main */
{
/* declaración de variables. */
int z;
int w;
/* Sentencias ejecutables. */
z=y+x;
w=y-x;
printf("\n\nz=%d \nw=%d \n\n ",z,w);
}
En este ejemplo "x" e "y" están definidas mientras que las variables "z" y "w" están sólo
declaradas.
2.4.-FICHEROS FUENTE.
Los programas fuente pueden se divididos en uno o más ficheros fuente. Un fichero fuente "C"
es un texto que contiene todo o parte del contenido de un programa fuente C. Un fichero fuente puede
contener por ejemplo algunas de las funciones necesitadas por el programa. Los ficheros fuente
separados se pueden combinar para formar ficheros fuente mayores usando la directiva #include.
Vemos un ejemplo de un programa fuente "C" formado por dos ficheros fuente. Las funciones main()
y max() forman ficheros fuente separados, la ejecución comienza en la función main().
/* -------------------------------------------
Fichero fuente 1 - Función Principal
------------------------------------------- */
#define UNO 1
#define DOS 2
#define TRES 3
/* Declaración de la función. */
extern int mayor(int, int);
main()
/* Definición de la función main() */
{
int w=UNO,x=DOS,y=TRES;
int z=0;
z=mayor(x,y);
w=mayor(z,w);
printf("\n Valor de z : %d ",z);
printf("\n Valor de w : %d ",w);
printf("\n\n");
}
/* ------------------------------------------
Fichero fuente 2 - Función mayor
------------------------------------------ */
int mayor(a,b)
/* Definición de la función */
int a,b;
{
if (a>b)
return(a);
else
return(b);
}
En el primer fichero fuente, la función mayor() se declara sin haber sido definida. Esto se
conoce con el nombre de "declaración hacia delante". La definición de la función main() incluye
llamadas a la función mayor().
Las líneas que comienzan con el signo # son directivas del procesador, que fuerzan al
preprocesador a sustituir los identificadores UNO,DOS y TRES con el número especificado por todo
el cuerpo del programa con anterioridad a su compilación.
Una vez creados se pueden compilar, linkar y ejecutar con las órdenes:
2.5.-EJECUCIÓN DE UN PROGRAMA.
Todo programa tiene que tener una función principal ( main() ). Esta función es el punto de
comienzo de la ejecución del programa y este normalmente acaba su ejecución al final de esta función,
sin embargo la ejecución puede finalizar en otro punto del programa dependiendo del entorno de
ejecución. Los programas normalmente tienen más de una función y cuando una función es invocada
la ejecución comienza en la primera sentencia de la función llamada, la función retorna el control a la
línea siguiente a la llamada cuando se ejecuta una sentencia "return" o bien se encuentra el fin de la
función.
Todas las funciones, incluyendo main(), pueden declararse con parámetros. Las funciones
llamadas por otras funciones reciben valores para sus parámetros, si es el caso. Los parámetros de la
función main() se declaran para recibir valores pasados desde fuera del programa. ( Por ejemplo desde
la línea de comando cuando se invoca la ejecución del programa).
Tradicionalmente los tres primeros parámetros de la función main() se declaran con los
nombres argc, argv y envp. El parámetro argc almacena el número de argumentos o valores pasados a
la función main(). El parámetro argv es un array de punteros donde cada elemento es un puntero a una
cadena de los argumentos pasados a main().
Una variable se dice que es "visible" en un bloque o fichero fuente si el tipo y su nombre son
reconocidos en dicho bloque o fichero fuente.
Las declaraciones y definiciones dentro de un bloque se dice que son a "nivel interno",
mientras que si ocurren fuera de todos los bloques se dice que son a "nivel externo". Todas las
variables que se declaran a nivel externo son globales mientras que si se declaran en un nivel interno
son locales. Sin embargo los almacenamientos específicos static y extern se pueden utilizar para
declarar variables globales o referenciar a una variable global dentro de un bloque.
En general: Las variables declaradas o definidas en un nivel interno son visibles desde el
punto donde son declaradas o definidas hasta el final del bloque donde aparecen.
Si una variable declarada dentro de un bloque tiene el mismo nombre que una variable
declarada en un nivel externo, la variable local impera sobre la definida en el nivel externo dentro del
bloque; sin embargo al salir del bloque la visibilidad de la variable externa se restaura.
En este ejemplo, hay cuatro niveles de visibilidad: el nivel externo y tres niveles de bloques.
La función printf imprimirá los valores 1,2,3,0,3,2,1.
/* ---------------------
Programa: printfs.c
--------------------- */
#include <stdio.h>
main()
{
printf("\n\n");
printf(" %s %d\n","esto es una cadena",100);
printf(" esto es una cadena %d\n",100);
printf(" El numero %d es decimal,y %f es float.\n",10,110.789);
printf(" %c %s * %d - %x \n",'*',"numero decimal y hexadecimal:",10,10);
printf("\n\n\n");
printf(" El numero 123 con formato %%10d es :-> %10d \n",123);
printf(" El numero 123 con formato %%-10d es :-> %-10d \n",123);
printf("\n\n");
printf(" La cadena \"Hola\" con formato %%-10s es :-> %-10s \n", "Hola");
printf(" La cadena \"Hola\" con formato %%10s es :-> % 10s \n", "Hola");
printf(" La cadena \"123456789\" con formato %%5.7s es :-> %5.7s
\n","123456789");
printf("\n\n");
printf(" El numero 123.234 con fomato %%5.2e es :-> %5.2e \n",123.234);
printf(" El numero 123.234 con fomato %%5.2g es :-> %5.2g \n",123.234);
printf(" El numero 123.234 con fomato %%-5.2e es :-> %-5.2e \n",123.234);
printf(" El numero 123.234 con fomato %%-5.2g es :-> %-5.2g \n",123.234);
}
CAPÍTULO 3
VARIABLES Y TIPOS EN “C”
3.1.-INTRODUCCION.
Este capítulo describe la forma de efectuar las declaraciones de variables, funciones y tipos en
C. Las declaraciones en "C" tienen el formato general:
[almacenmiento][tipo]declarador=[inicial][,declarador...]
Donde:
declarador es un identificador que puede ser modificado con los caracteres "[]","*" o "()" para
declarar un array, un puntero o un tipo de función. Cuando se declara una variable
simple (ej. carácter,entero,real), o una estructura o union de variables simples,
declarador esta compuesto solo por un identificador.
Todas las variables en "C" tienen que ser declaradas antes de usarse.
Las funciones "C" pueden declararse de forma explícita en una declaración de función o de
forma implícita llamando a la función antes de ser declarada o definida.
El lenguaje "C" define un conjunto estándar de tipos de datos, sin embargo se podrán definir
nuevos tipos en base a los tipos ya definidos.
3.2.-TIPOS.
El lenguaje "C" dispone de un conjunto básico de tipos de datos, llamados "tipos
fundamentales". Son los siguientes:
TIPOS FUNDAMENTALES.
El tipo void sólo se utiliza para declarar funciones que no retornan ningún valor. Los tipos de
la tabla "integrales" y "Punto_flotante" se utilizan para declarar variables y funciones. Posteriormente
veremos que se pueden crear tipos adicionales usando en la declaración typedef.
Algunos de los tipos de la tabla anterior se pueden abreviar según la siguiente tabla:
Tipo Abreviación
---------------------------------------
char --
int --
short int short
long int long
unsigned char --
unsigned int --
unsigned short unsigned short
unsigned long int unsigned long
float --
long float double
La tabla que figura a continuación contiene el tamaño de almacenamiento asociado a cada tipo
fundamental y contiene el rango de posibles valores que se pueden almacenar en ellos.
El tipo char se usa para almacenar una letra, dígito o símbolo del conjunto representable de
caracteres. El valor entero de un carácter es el código ASCII correspondiente a ese carácter.
Nótese que los tipos int y unsigned int no están definidos por el lenguaje C, ya que sus
tamaños se corresponden con el tamaño natural de un entero en una máquina dada. Por ejemplo en una
máquina de 16 bits el tamaño del tipo int son 2 bytes (16 bits), si la maquina es de 32 bits el tamaño
usual del tipo int son 4 bytes (32 bits), de esto se deduce que dependiendo de la máquina el tipo int
puede ser equivalente al tipo short int o al tipo long int. Lo mismo sucede con el tipo unsigned int que
puede ser equivalente a unsigned short o unsigned long.
ú Los números de punto flotante (reales) usan el estándar IEEE ( Institute of Electrical
and Electronics Engineers, inc.), que para los valores de tipo float utiliza 4 bytes. El máximo valor que
se puede representar es normalmente 1.701411E38. Para los valores de tipo double utilizan 8 bytes.
/* -----------------
Programa: tipos.c
----------------- */
/* .. ..
Indica el tamaño de los tipos basicos del Turbo C.
.. .. */
main()
{
printf("\n\n\n");
printf(" TIPOS EN TURBO C \n");
printf(" ................ \n\n\n");
printf(" El tipo ( Int ) ocupa :( %d )bytes.\n",sizeof(int));
printf(" El tipo ( Short ) ocupa :( %d )bytes.\n",sizeof(short));
printf(" El tipo ( long ) ocupa :( %d )bytes.\n",sizeof(long));
printf(" El tipo ( float ) ocupa :( %d )bytes.\n",sizeof(float));
printf(" El tipo ( double ) ocupa :( %d )bytes.\n",sizeof(double));
printf(" El tipo ( Char ) ocupa :( %d ) byte.\n",sizeof(char));
printf("\n\n");
printf(" unsigned short int -> (%d) bytes.\n",sizeof(unsigned shortint));
printf(" unsigned long int -> (%d) bytes.\n",sizeof(unsigned long int));
printf(" unsigned int -> (%d) bytes.\n",sizeof(unsigned int));
printf(" unsigned char -> (%d) byte.\n",sizeof(unsigned char));
printf("\n\n");
}
Ejemplo2: Un ejemplo a tener en cuenta acerca de los límites de desbordamiento de los tipos.
/* --------------------
Programa: sinsigno.c
-------------------- */
/* .. ..
Este ejemplo presenta un caso de
desbordamiento para el tipo
short e int.
.. .. */
#include <stdio.h>
main()
{
short int i;
unsigned int j;
int k;
j=32768;
i=j;
k=j;
printf("\n\n\n");
printf(" Valor de i = %d \n\n ",i);
printf(" Valor de j = %u \n\n ",j);
printf(" Valor de k = %d \n\n ",k);
}
3.3.-DECLARADORES.
Los declaradores permiten a los programadores declarar arrays de valores, punteros a valores y
funciones que retornan valores de un tipo especifico. Un declarador es un identificador modificado
con alguna combinación de "[]","()", y "*" para declarar un array, puntero o tipo de función.
Formatos:
identificador
declarador[]
declarador[constante-expresion]
*declarador
declarador()
declarador(arg-tipo-lista)
(declarador)
3.4.-DECLARACIÓN DE VARIABLES.
En este apartado se describe el formato y significado de la declaración de las variables:
- Variables simples.
- Variables Enumeraciones.
- Estructuras.
- Uniones.
- Arrays.
- Pointers.
[almacenamiento]tipo declarador[declarador...]
Donde:
Siguen el formato:
Ejemplos:
int x;
unsigned long var1,var2;
double otra;
Ejemplo: En el siguiente ejemplo se utiliza la sentencia define para definir valores constantes
utilizados posteriormente para inicializar definiciones de variables.
/* ---------------------
Programa: constante.c
--------------------- */
#define NOMBRE 'J' /* Carácter */
#define APELLIDO_1 'G' /* Carácter */
#define APELLIDO_2 'P' /* Carácter */
#define SALUDO "Buenas tardes" /* cadena */
#define PESETAS 158 /* entero decimal */
#define ENTERO_LARGO 4596538 /* entero largo decimal */
#define ENTERO_1 0x17 /* entero hexadecimal */
#define ENTERO_2 011177 /* entero octal */
main() /* definicion del programa principal */
{
char a=NOMBRE,b=APELLIDO_1,c=APELLIDO_2;
int i=ENTERO_1, j=ENTERO_2;
long x= ENTERO_LARGO;
printf("\n\n");
printf(" :<->: - %s ! :<->:",SALUDO);
printf("\n\n las iniciales de mi nombre son %c.%c.%c.",a,b,c);
printf("\n\n Tengo %d a$os.",i);
printf("\n\n y necesito %d %c ",j,PESETAS);
printf("\n\n Mi telefono es %ld. ",x);
printf("\n\n");
}
3.4.2.-DECLARACIÓN DE ENUMERACIONES.
enum [nombre]{lista_enum}identificador[,identificador...];
enum nombre identificador[,identificador..];
donde:
En el primer formato:
Sigue el formato:
identificador[=constante-expresión]
[,identificador[=constante-expresión]]
.
.
.
Sin embargo esta secuencia se puede modificar con la opción "=constante-expresión", la cual
si aparece asocia al identificador el valor; dicha constante-expresión tiene que ser de tipo int y puede
ser de tipo negativo, y a partir de aquí el siguiente identificador toma el valor "constante-expresión +
1", sino se le da otro valor. Dos identificadores pueden contener valores de constate duplicados.
En el segundo formato:
Ejemplos:
1: enum dia {
sabado,
domingo=0,
lunes,
martes,
miercoles,
jueves,
viernes
} dia_trabajo;
2: hoy=miercoles;
3: enum dia vacaciones;
El primer ejemplo define una enumeración del tipo dia y declara la variable dia_trabajo de ese
tipo. El valor 0 se asocia al sabado por defecto. El identificador domingo se asocia de forma explícita
al valor 0. Los restantes identificadores llevan asociados los valores de 1 a 5 por defecto.
En el último ejemplo se declara un variable de nombre vacaciones del tipo enum dia, dicha
enumeración tiene que ser previamente declarada.
/* -------------------
Programa: enumera.c
------------------- */
# include <stdio.h>
enum dineros {peseta,
duro,
diez,
veinticinco=3,
cincuenta,
cien=450,
doscientas,
quinientas};
char nombre_moneda[][20]={
"peseta",
"duro",
"diez",
"veinticinco",
"cincuenta",
"cien",
"doscientas",
"quinientas"
};
main()
{
enum dineros monedas;
printf("\n\n\n");
printf(" peseta = %d, duro = %d, diez = %d \n\n", peseta,duro,diez);
printf("veinticinco=%d,cincuenta= %d, cien = %d \n\n", veinticinco,cincuenta,cien);
printf(" doscientas = %d, quinientas = %d \n\n", doscientas,quinientas);
printf(" %15s ",nombre_moneda[peseta]);
printf(" %15s ",nombre_moneda[duro]);
printf(" %15s \n\n",nombre_moneda[quinientas]);
}
La razón principal para utilizar tipos enum es ayudar a la legilibilidad de los programas. Si
estamos tratando con alguna especie de código es más obvio trabajar con los valores de ese código que
los números asociados. Los tipos enum funcionan normalmente para uso interno y no para operaciones
de E/S.
3.4.3.-DECLARACIÓN DE ESTRUCTURAS.
Donde:
La opción decla_lista_miem es una lista de una o más variables. Cada variable declarada en
esta lista se define como un miembro del tipo de estructura. Los miembros de la estructura pueden
tener cualquier tipo conocido: fundamental, array, puntero, union o estructura. No se permite que un
miembro tenga el tipo de la estructura en la cual aparece, sin embargo un miembro puede declararse
como un puntero al tipo de estructura en el cual aparece..
Ejemplos:
1: struct {
float x,y;
} complejo;
2: struct ejemplo {
char nombre[20];
int var1;
long modelo;
} tiempo;
3: struct ejemplo estudiante, facultad, reunion;
4: struct ejemplo {
char c;
float *pf;
struct ejemplo *proximo;
} r;
El primer ejemplo define una variable estructura de nombre complejo. La estructura tiene dos
miembros x e y de tipo float. El tipo estructura no tiene nombre. En el segundo ejemplo el nombre de
la estructura es ejemplo.
El tercer ejemplo define tres variables, estudiante, facultad y reunión del tipo de estructura
ejemplo definido anteriormente en el ejemplo2.
Mientras que en el cuarto ejemplo se define la variable r del tipo struct ejemplo en el cual hay
un miembro que esta declarado como un puntero al tipo de estructura que esta siendo definido.
Ejemplo: Vemos un ejemplo de declaración de una estructura y su utilización con las sentencias
printf(), scanf() y sizeof().
/* ------------------
Programa: struct.c
------------------ */
main()
{
struct personal /* define el tipo 'struct personal' */
{
char nombre[30];
long int dni;
} empleado1, empleado2; /* define las variables
/*'empleado1','empleado2' */
/* del tipo struct personal */
printf("\n\n\n");
printf("Tamaño del tipo struct personal (%d) octetos\n\n", sizeof(struct
personal)); ║
printf("nombre empleado1 = ");
gets(empleado1.nombre);
printf("nombre empleado2 = ");
gets(empleado2.nombre);
printf("DNI empleado1 = ");
scanf("%ld",&empleado1.dni);
printf("DNI empleado2 = ");
scanf("%ld",&empleado2.dni);
printf("\n\n\n");
printf(" NOMBRE DEL EMPLEADO1 = <%10s> \n", empleado1.nombre);
printf(" D.N.I. = <%10ld> \n\n",empleado1.dni);
printf(" NOMBRE DEL EMPLEADO2 = <%10s> \n", empleado2.nombre);
printf(" D.N.I. = <%10ld> \n",empleado2.dni);
}
3.4.4.-DECLARACIONES DE UNIONES.
La declaración de una unión define el nombre de la variable unión y especifica los miembros
de la unión los cuales pueden tener diferentes tipos. Una variable del tipo union almacenará uno, y
solo uno, de los valores simples definidos en la unión.
union[nombre]{decla_lista_miem}declarador[,declarador...];
union nombre declarador[,declarador...];
Ejemplos:
1: union signo {
int var1;
unsigned var2;
} numero1;
2: union {
*a,b;
float f[20];
} numero2;
3: union {
struct {
char var1;
unsigned color:4;
} ventana1,ventana2,ventana3,ventana4;
} pantalla[25][80];
El primer ejemplo define una variable unión "numero1" con dos miembros: var1 (entero con
signo), var2 (entero sin signo). En este caso el valor que almacene la variable número se podrá
almacenar como un entero con signo o sin signo. El tipo de unión se llama signo.
El último ejemplo define un array, pantalla, de dos dimensiones de uniones. El array contiene
2000 elementos cada uno de los cuales es una unión con cuatro miembros:
ventana1,ventana2,ventana3,ventana4 donde cada uno de los miembros es una estructura. Cada
elemento de la unión almacenará uno de los cuatro posibles miembros de la estructura cada vez.
Ejemplo: En este ejemplo se declara una union y muestra un caso de utilización de sus
miembros.
/* -----------------
Programa: union.c
----------------- */
main()
{
union entero_flotante /* define el tipo 'union */
/* entero_flotante' */
{
int entero;
float flotante;
} x; /* define la variable 'x' del tipo */
/* union entero_flotante */
printf("\n\n\n");
printf("Tamaño del tipo union entero_flotante (%d) octetos\n",sizeof(union
entero_flotante));
x.entero=1350;
printf("\nMiembro x.entero ..... %d",x.entero);
x.flotante=12.83;
printf("\nMiembro x.flotante ... %.2f\n\n",x.flotante);
}
3.4.5.-DECLARACIONES DE ARRAYS.
La declaración de un array consiste en definir el nombre del array y el tipo de cada uno de los
elementos. Se puede definir también el número de elementos del array. Una variable del tipo array es
un puntero al tipo de los elementos del array.
tipo declarador[constante-expresión];
tipo declarador[];
Donde:
declarador: Es el nombre del array, donde los corchetes que siguen indican tipo array.
tipo: Es el tipo de cada elemento individual del array. No se permite el tipo void.
tipo declarador[cost_expres][const_expres]...
Por ejemplo el siguiente array consta de 3 filas con dos columnas. En él las dos columnas de la
primera fila se almacenan primero, luego las dos columnas de la segunda fila y por último las dos
columnas de la tercera fila.
char matriz[3][2];
Ejemplos:
En el primer ejemplo se define la variable marca como un array con 10 elementos de tipo
entero.
El segundo ejemplo define matriz como un array bidimensional de 150 elementos de tipo
float.
El tercer ejemplo define un array de estructuras de 100 elementos, donde cada elemento es una
estructura con dos miembros.
/* ------------------
Programa: array1.c
------------------ */
main()
{
int x[10];
int t;
printf("\n\n");
for(t=0;t<10;t++)
{
x[t]=t;
printf("\n El valor de x[%d] es = %d \n",t,x[t]);
}
}
/* -----------------
Programa array2.c
----------------- */
# include <stdio.h>
main()
{
long ejemplo[10],i,media;
for (i=0;i<4;i++) {
printf("introducir un numero %d: ",i);
scanf("%ld",&ejemplo[i]);
}
media=0;
for (i=0;i<4;i++) media=media+ejemplo[i];
printf("\n\n\n\n La media es %ld \n",media/4);
}
/* -----------------
Programa array3.c
----------------- */
char ch[7];
main()
{
int i;
for (i=0;i<7;i++)
{
ch[i] = 'A'+i;
}
printf("\n\n\n\n ");
for (i=0;i<7;i++)
printf("- %c ", ch[i]);
printf(" -");
printf("\n\n");
}
3.4.6.-DECLARACIONES DE PUNTEROS.
Siguen el formato:
tipo *declarador;
Donde:
tipo: es el tipo del objeto al que apunta, puede ser algún tipo fundamental, estructura o
unión.
Ejemplos:
1: char *mensaje;
2: int *punteros[10];
3: int (*puntero)[10];
4: struct lista *proximo, *previo;
5: struct lista {
char *anillo;
int contador;
struct lista *proximo;
} línea;
En el primer ejemplo se define la variable puntero mensaje que apunta a una variable de tipo
char.
El tercer ejemplo define la variable puntero "puntero" que apunta a un array de 10 elementos
de tipo int.
El cuarto ejemplo define dos variables puntero que apuntan a una estructura de tipo lista.
El quinto ejemplo declara la variable línea como un tipo de estructura lista. El tipo de
estructura lista esta definido con tres miembros, el primero es un puntero a un valor char, el segundo
es un valor entero y el tercero es un puntero a una estructura de tipo lista.
Ejemplo1: Trabajando con punteros podremos trabajar con las direcciones de almacenamiento de
la variables.
/* ------------------
Programa: punte1.c
------------------ */
# include <stdio.h>
main()
{
int *cont_dir, cont, val;
cont = 100;
cont_dir = &cont;
val = *cont_dir;
printf("\n cont = %d \n",cont);
printf("\n &cont = %d \n",&cont);
printf("\n cont_dir = %d \n",cont_dir);
printf("\n *cont_dir = %d \n",*cont_dir);
printf("\n Valor = %d \n",val);
}
/* ------------------
Programa: punte2.c
------------------ */
# include <stdio.h>
main()
{
int x;
int *p1,*p2;
x=100;
p1 = &x;
p2 = p1;
/* .. ..
Imprimimos el valor hexadecimal de la direccion de x.
.. .. */
printf("\n\n\n La direccion de <x> es <%x> .",p2);
/* .. ..
Imprimimos el valor de x.
.. ..*/
printf("\n\n Su contenido es <%d> .\n\n",*p2);
}
3.5.-DECLARACIONES DE FUNCIONES.
Consisten en definir el nombre y el tipo que retorna una función y posiblemente establecer los
tipos y número de argumentos de la función. Las declaraciones de funciones pueden incluir los tipos
de almacenamiento extern o static, discutidos más adelante.
Siguen el formato:
[tipo] declarador([lista_tipos_arg])[,declarador...];
Donde:
[nombre_tipo][,nombre_tipo...][,]
El primer nombre_tipo es el tipo del primer argumento de la función, el segundo es el tipo del
segundo argumento y así sucesivamente. Si lista_tipos_arg termina con una coma, el número de
argumentos de la función es variable sin embargo a de tener por lo menos un número de argumentos
equivalente al especificado antes de la coma. Si lista_tipos_arg contiene solo una coma, el número de
argumentos de la función es variable y puede ser cero.
El nombre_tipo para un tipo fundamental, un tipo estructura o un tipo unión consiste solo en
especificar solo el tipo (ej. int). El nombre_tipo para punteros,arrays y funciones están formados por la
combinación del tipo especificado con un "declarador abstracto" por fuera del identificador.
El tipo especial void se puede usar en lugar de lista_tipos_arg para declarar una función que
no tiene argumentos. La frase "void *" puede ser la lista_tipos_arg y especifica un argumento de algún
tipo puntero.
La lista_tipos_arg puede omitirse, sin embargo los paréntesis deben figurar en la declaración
vacíos, en este caso no se establecen ni el número ni el tipo de los argumentos de la función. En este
caso el compilador no ejecuta el chequeo de tipos entre los argumentos de la llamada a la función y los
parámetros formales de la definición de la función.
Las funciones pueden retornar valores de cualquier tipo excepto arrays y funciones. El
identificador de la función puede ser modificado con uno o más asteriscos "*" para declarar el retorno
de un tipo puntero. Las funciones no pueden retornar arrays y funciones, como comentábamos, sin
embargo si pueden retornar punteros a arrays y punteros a funciones.
Ejemplos: Prototipos.
El primer ejemplo declara la función sumar con dos argumentos enteros y que retorna un valor
entero.
El segundo ejemplo declara la función encontrar_cadena que retorna un puntero a un valor
tipo char. La función lleva al menos un argumento, un puntero a un valor char.La función puede llevar
más argumentos.
El tercer ejemplo declara una función que no retorna ningún valor y que no lleva argumentos.
La función sumar del ejemplo cuatro retorna un puntero a un array de tres valores tipo double,
y lleva dos argumentos de tipo float.
El quinto ejemplo declara la función seleccion, sin argumentos, que retorna un puntero a una
función que retorna un puntero. El puntero que retorna apunta a una función que lleva un argumento
int y retorna un valor int.
En el último ejemplo, la función imprimir se declara para llevar un argumento de cualquier
tipo puntero y retornar un entero. En este caso los puntero declarados en el ejemplo, letra (char) y
número (short), podrían serle pasados como argumentos a la función imprimir sin que se produzcan
errores.
/* --------------------
Programa: funcion1.c
-------------------- */
/* .. ..
Un sencillo programa con dos
funciones.
.. .. */
void hola(void);
main()
{
printf("\n\n\n\n");
hola();
}
/* Definicion de la función hola */
/* ----------------------------- */
void hola()
{
printf(" hola \n\n");
printf(" Bienvenido al lenguaje C. ");
printf("\n\n\n");
}
/* --------------------
Programa: funcion2.c
-------------------- */
void multiplicar(int, int);
main()
{
printf("\n\n\n\n");
multiplicar(10, 11);
}
void multiplicar(a, b)
int a, b;
{
printf(" El producto %d x %d es = %d \n\n\n", a, b, a*b);
}
/* -------------------
Programa funcion3.c
------------------- */
/* .. ..
Este programa refleja el uso de la sentencia
return en las funciones de C.
.. .. */
int mul(int, int);
main()
{
int respuesta,a=10,b=11;
respuesta = mul(a,b);
printf("\n\n\n");
printf(" El producto %d x %d tiene por \n\n",a,b)
printf(" respuesta %d \n",respuesta);
}
/* Definicion de la función mul */
/* ---------------------------- */
mul(t,p)
int t;
int p;
{
return t*p;
}
/* --------------------
Programa: funcion4.c
-------------------- */
int abs(int); /*Prototipo*/
main()
{
int a = 10,b = 0,c = - 22;
int d,e,f;
d = absoluto(a);
e = absoluto(b);
f = absoluto(c);
printf("\n\n\n");
printf(" -----------------------------------------\n");
printf(" -> Valor absoluto %4d es %4d - \n", a, d);
printf(" -> Valor absoluto %4d es %4d - \n", b, e);
printf(" -> Valor absoluto %4d es %4d - \n", c, f);
printf("------------------------------------------");
printf("\n\n\n");
}
/* Función valor absoluto */
/* ====================== */
absoluto(x)
int x;
{
int y;
y=(x<0)?-x:x;
return(y);
}
3.6.-CLASES DE ALMACENAMIENTO.
Una variable global es aquella que esta siempre presente durante la ejecución del programa.
Todas las funciones son globales.
Las variables locales solo están presentes en el bloque en el que son definidas.
- auto.
- register.
- static.
- extern.
Los almacenamientos auto y register son locales y los almacenamientos static y extern son
globales.
La declaración de variables a nivel externo usan las clases de almacenamiento static y extern o
se omite este. Por tanto el almacenamiento auto y register no se permiten a nivel externo.
Una vez definida la variable a nivel externo esta es reconocida en todo el programa en el que
aparece.
La clase de almacenamiento extern se usa para declarar una referencia a una variable definida
en otra parte, puede ser declarado por ejemplo para hacer visible una variable fuera de su definición.
Una referencia a una variable con extern hace que la variable sea visible fuera del alcance del fichero
fuente en la cual se declara.
Las declaraciones que contienen la clase de almacenamiento extern no permiten inicializar las
variables ya que sus valores estarán previamente definidos.
Ejemplo:
Programa: fuente1.c
/*---------------------------*
Fichero fuente uno
*---------------------------*/
extern int i;
/* Referencia a i , definida abajo */
main()
{
i++; /* i vale 4 */
printf("%d \n",i);
proximo();
}
int i=3; /* Definición de i */
proximo()
{
i++;
printf("%d \n",i); /* i vale 5 */
otro();
}
Programa: fuente2.c
/*---------------------------*
Fichero fuente dos
*---------------------------*/
/* referencia a i del primer fichero fuente */
extern int i;
otro()
{
i++;
printf("%d \n",i); /* i vale 6 */
}
El almacenamiento auto define a las variables como locales, con los cual solo son visibles
dentro del bloque en el cual son declaradas. Las variables con almacenamiento auto no se inicializan
automáticamente en tiempo de compilación por ello si no se inicializan las variables auto estarán
indefinidas.
El número de registros que se pueden utilizar para almacenar variables depende de la máquina.
Si no se disponen de registros cuando el compilador encuentra una declaración register, a la variable
se le asigna el almacenamiento auto y se almacena en memoria. El almacenamiento register (si es
posible) solo esta garantizado para los tipos int y puntero.
Una variable declarada a nivel interno con almacenamiento static tiene tiempo de vida global,
sin embargo solo es visible dentro del bloque en el cual se declara. Es decir la variables declaradas
como static retienen el valor cuando la ejecución del bloque finaliza, a diferencia de las variables auto.
Las variables son inicializadas a 0 en tiempo de ejecución si explícitamente no se hace.
Una variable declarada con almacenamiento extern es una referencia a una variable con el
mismo nombre definida a nivel externo en alguno de los ficheros fuente. El propósito de una
declaración extern interna es hacer visible una variable de nivel externo dentro del bloque.
Ejemplo:
Programa interno.c
int i=1;
main()
{ /*referencia a i; definida a nivel externo. */
extern int i;
/* a toma valor inicial 0 y solo es visible dentro de la función main() */
static int a;
/* b es almacenado en un registro, si es posible */
register int b=0;
/* El almacenamiento por defecto es auto. */
int c=0;
/* Se imprimiran los valores: 1,0,0,0 */
printf("%d\n%d\n%d\n%d\n",i,a,b,c);
otra();
otra();
otra();
}
otra
{
/* i es redefinida */
int i=16;
/* Esta variable solo es visible dentro de otra() */
static int a=2;
a+=2;
/* Imprime los valores 16,4 */
printf("%d\n%d\n",i,a);
}
# cc -compat interno.c
# ./a.out
La variable "i" esta definida a un nivel externo con un valor 1. Con una declaración extern se
hace un referencia al nivel externo de la variable "i". La variable static "a" se inicializa
automáticamente a 0, con lo cual no es necesario iniciarla a 0 explícitamente.
Dentro de la función otra() la variable "i" se redefine como una variable local con valor inicial
16; esto no afecta a la "i" de nivel externo. la variable "a" se declara como una variable static
inicializada a 2 y esta tampoco tiene ningún conflicto con la variable "a" del main() ya que la
visibilidad de las variables static a nivel interno se reduce al bloque en el cual son declaradas.
3.6.3.-DECLARACIÓN DE FUNCIONES.
En la declaración de funciones se pueden usar las clases de almacenamiento static o extern. Las
funciones siempre son globales. Las funciones declaradas a nivel interno tienen el mismo significado
que las funciones declaradas a nivel externo.
Una función declarada como static solo es visible en el fichero fuente en el cual es definido,
por ello puede haber dos funciones declaradas como static en dos fichero fuente diferentes sin
conflicto. Las funciones declaradas como extern son visibles dentro de todos los ficheros fuente que
constituyen el programa. Las funciones que omiten la clase de almacenamiento en la declaración son
declaradas como por defecto como extern.
3.6.4.-DECLARACIONES COMPLEJAS.
A veces las declaraciones pueden llevar paréntesis, los cuales se usan para especificar
interpretaciones particulares de una declaración compleja. Varias combinaciones de los tipos array,
puntero y funciones pueden aplicarse a un mismo identificador, sin embargo algunas combinaciones
son ilegales como que un array este compuesto de funciones o que una función retorne un array o una
función.
Una regla simple para interpretar declaraciones complejas es la siguiente: Leer las
declaraciones de dentro a fuera, comenzar con un identificador y mirar si tiene corchetes o
paréntesis a la izquierda y si existen interpretar estos, luego buscar si tiene asteriscos a la
izquierda. Si se encuentra algún paréntesis derecho en algún momento, volver y aplicar las
reglas anteriores a todo lo que figura dentro de los paréntesis. Por último aplíquese el tipo
especificado. Vemos un ejemplo complejo:
char *(*(*variable)())[10]
7 6 4 2 1 3 5
Quizá el ejemplo anterior asuste un poco, por ello vemos unos ejemplos más sencillo de los
conceptos anteriores:
En el primer ejemplo los "[]" tienen mayor prioridad que el "*", por eso el identificador vector
es un array. El asterisco (puntero) se aplica al tipo de los elementos del array que son valores enteros.
En el segundo ejemplo el asterisco tiene mayor prioridad que los corchetes debido a los
paréntesis, por ello el identificador punte es un puntero a un array de 5 elementos enteros.
El modificador de función, los "()", tienen mayor prioridad que los asteriscos, así que en el
tercer ejemplo el identificador mayor es una función, con dos valores long como argumentos, que
retorna un puntero a un valor tipo long. Mientras que el cuarto ejemplo es similar al segundo, por ello
el identificador variable es un puntero a una función que retorna un valor tipo long; la función tiene
también dos argumentos tipo long.
Un puntero puede apuntar a otro puntero y un array puede contener otros arrays como se ve en
el ejemplo número cinco, en donde el identificador "vector" es un array de 5 elementos donde cada
uno de ellos es un array de 5 elementos punteros a punteros a uniones de dos miembros.
El sexto ejemplo muestra como la localización de los paréntesis altera el significado de una
declaración, en él el identificador "clien" es un array de 5 elementos punteros a arrays de 5 elementos
de puntero a uniones.
3.7.-INICIALIZACIÓN.
Una variable puede inicializarse a un valor inicial asignándoselo en la declaración de la
variable. dicho(s) valor(es) son precedidos por signo "=" en la declaración.
=valor(es)
Todos los tipos de variables pueden ser inicializadas, respetando una serie de restricciones
comentadas a continuación.
La inicialización de las variables con almacenamiento auto y register se ejecuta cada vez que el
control de ejecución pasa por el bloque en el cual están declaradas. Si no se inicializan su valor inicial
es indefinido.
La inicialización con almacenamiento auto de arrays, estructuras y uniones esta prohibida. Sólo
si están definidas con almacenamiento static pueden ser inicializados.
Siguen el formato:
var=expresión.
Ejemplos:
1: int x=10;
2: register int *apuntador=0;
3: int c=(3*1024);
4: int *k=&x;
Formato:
var={lista_valores};
Donde lista_valores son los valores de inicialización separados por comas. Cada valor puede se
una constante, expresión o otra lista_valores entre "{}".
Los valores de lista_valores se asignan en orden a los miembros de la variable del tipo
agregado. Si hay menos valores lista_valores que en el tipo agregado, los restantes miembros se
inicializan a 0. El caso contrario produce un error.
Ejemplo:
En este ejemplo matriz es un array 4x3 con las cuatro filas inicializadas. Nótese que
lista_valores de la tercera y cuarta filas contienen comas después de la última constante y que el
último lista_valores "{4,4,4,}" va seguido de coma. Esta comas extras están permitidas pero no se
requieren en la declaración. Solo son requeridas las comas que separan los valores dentro de
lista_valores.
int matriz[4][3]={
1,1,1,2,2,2,3,3,3,4,4,4
};
Ejemplos:
1: struct lista {
int i,j,k;
float m[2][3];
} var1= {
1,
2,
3,
{4.0,4.0,4.0}
};
2: union
char x[2][3];
int i,j,k;
} var2= {
{'1'},
{'6'}
};
3.7.3.-INICIALIZACIÓN DE CADENAS.
char codigo[]="xyz";
Código es un array de cuatro valores carácter. Recordar que el cuarto elemento es el carácter
null, el cual termina todas las cadenas.
Si el tamaño del array se especifica y la cadena es mayor que el tamaño indicado los caracteres
extra son descartados. En el siguiente ejemplo se inicializa la cadena extra con los tres primeros
caracteres de "rstuvwxyz".
char extra[3]="rstuvwxyz";
En este ejemplo los caracteres "uvwxyz" y null son descartados. Algunos compiladores
devuelven un error en este tipo de inicializaciones.
Si la cadena es menor que el tamaño indicado en el array, los restantes elementos del array se
inicializan a 0 (Carácter null).
3.8.-DECLARACIÓN DE TIPOS.
Una declaración de tipo define el nombre y los miembros de un tipo estructura o unión, o el
nombre y el conjunto enumeración de un tipo enumeración. El nombre de un tipo declarado se puede
usar en las declaraciones de variables o funciones para referirse a ese tipo. Esto se usa cuando las
funciones y variables tienen el mismo tipo.
Una declaración typedef define un tipo especificado para un tipo. Estas declaraciones se usan
para configurar un mayor o menor significado para los tipos ya definidos por el "C" o por los tipos ya
declarados por el usuario.
Ejemplos:
1: enum estados {
perdido=-1,
var1,
var2=0,
ganar
};
2: struct estudiante {
char nombre[20];
int ind,clase;
};
El primer ejemplo declara un tipo enumeración llamado estados. El nombre del tipo se puede
usar en declaraciones de variables enumeración. El identificador perdido tiene el valor -1 asignado;
var1,var2 toman el valor 0, la variable ganar toma el valor 1.
El segundo ejemplo declara un tipo estructura de nombre estudiante. Las variables estructuras
pueden ser declaradas del tipo estudiante, como por ejemplo:
Siguen el formato:
sinónimos para los nombres de tipos existentes. Todos los tipos pueden ser declarados con typedef,
incluyendo los tipo puntero, funciones y arrays. La orden typedef para un tipo puntero,una estructura
puede declararse antes de que el tipo puntero, estructura o unión sea definido.
Ejemplos:
DIBUJAR caja;
void caja(int, int);
3.9.-NOMBRES DE TIPOS.
Un nombre de tipo especifica un tipo de datos particular. Estos nombres se usan en tres
contextos: en la lista de tipos de argumentos de la declaración de funciones, en tipos cast y en
operaciones sizeof. Discutidas posteriormente.
Los nombres de tipo para los tipos fundamentales, enumeraciones, estructuras y uniones son
simplemente los especificadores de tipo para esos tipos.
tipo declarador_abstracto
Un declarador abstracto es un declarador sin un identificador, formado por uno o más punteros,
array o modificador de funciones. El modificador de puntero "*" siempre aparece antes del
identificador en el declarador, mientras que los "[]" en el array y los "()" en la función aparecen
después del identificador.
Los declaradores complejos pueden ser muy complicados, ya que los paréntesis tienen una
particular interpretación. El declarador abstracto "()" solo no se permite porque es ambiguo.
Ejemplos:
1: long *
2: double *(double, double)
3: int(*)[5]
4: int(*)(void)
El segundo ejemplo es el nombre para una función que lleva dos argumentos double y retorna
un puntero a un valor double.
El tercer y cuarto ejemplo muestran como los paréntesis modifican los declaradores abstractos.
El ejemplo 3 da el nombre para un puntero a un array de 5 valores enteros. El ejemplo 4 nombra un
puntero a una función sin argumentos y retorna un entero.
CAPÍTULO 4
OPERADORES Y EXPRESIONES
Notas: Los operadores son listados en orden descendente de precedencia. Los operadores que
aparecen en la misma línea tienen igual precedencia.
Cuando varios operadores con la misma procedencia aparecen al mismo nivel en una expresión
la evaluación se realiza de acuerdo a su asociatividad. Los operadores lógicos son evaluados de
izquierda a derecha evaluando el mínimo número de operandos necesarios para determinar el resultado
de la expresión, con lo cual algunos operandos de la expresión pueden no ser evaluados.
Por ejemplo en la expresión "x && y++", el segundo operando "y++" es evaluado sólo si x es
cierto (no 0). Nótese como en una expresión de este tipo "y" no será incrementada en el caso de que
"x" sea falso ( 0 ).
El tercer ejemplo es el más interesante, en él el operador lógico AND tiene mayor precedencia
que el operador lógico OR, por ello la expresión "q&&r" es evaluada primero antes que s--. De esto se
deduce que si "q&&r" toma valor distinto de "0" la expresión "s--" no es evaluada con lo cual la
variable "s" no decrementa su valor. Para solucionar esto se colocaría "s" como primer operando de
toda la expresión o bien se decrementaría el valor de "s" en una operación aparte
op_unariooperador
sizeof(operador)
Una expresión binaria se construye con dos operandos unidos por un operador.
Un expresión terciaria esta formada por tres operandos unidos por un operador ternario (? :).
operando?operando:operando
Los operadores de asignación unarios son el incremento (++) y decremento (--). Los
operadores de asignación binaria son el operador de asignación simple (=) y los operadores de
asignación compuesta. Cada operador de asignación compuesto es una combinación de otros
operadores binarios con el operador de asignación simple. Los formatos de las expresiones de
asignación simples son:
operando++
operando--
++operando
--operando
operando1=operando2
4.2.1-OPERADOR INCREMENTO.
El operador incremento realiza una tarea muy simple: incrementa el valor de su operando en 1.
Se ofrecen en "C" dos variedades. En la primera de ellas, el ++ aparece antes de la variable afectada,
es el llamado modo "prefijo". En la segunda, el ++ se encuentra situado detrás de la variable. A esta
variedad la denominaremos modo "sufijo". El operador incremento tiene la ventaja de generar un
código compilado ligeramente más eficiente, ya que su estructura se asemeja más al código máquina
real.
La diferencia entre ambos modos reside en el preciso momento en que se realiza la operación
de incremento. En primer lugar prestaremos atención a las semejanzas, y volveremos más adelante a
las diferencias. El ejemplo siguiente demuestra el funcionamiento de ambos operadores.
/* ------------------
Programa: incre1.c
------------------ */
main()
{
int ultra = 0, super = 0;
int a,b;
printf(" .......... .......... \n");
while (super < 6)
{
a = super++;
b = ++ultra;
printf(" super = %d, ultra = %d \n", a,b);
}
printf("\n\n\n");
}
/* ------------------
Programa: incre2.c
------------------ */
/*
.. ..
Este programa permite ver el uso del operador
incremento como prefijo y como sufijo.
.. ..
*/
main()
{
int a=1,b=1;
int amas, masb;
amas = a++; /* sufijo */
masb = ++b; /* prefijo */
printf("\n\n\n\n\n");
printf(" a amas b masb \n");
printf(" - ---- - ---- \n\n");
printf(" %3d %5d %5d %5d",a,amas,b,masb);
printf("\n\n\n\n\n");
}
a amas b masb
─ ──── ─ ────
2 1 2 2
donde tanto "a" como "b" se han incrementado en 1. Sin embargo "amas" contiene el valor de "a"
antes de que este fuera cambiado, en tanto que "masb" toma el valor de "b" tras el incremento. Por
tanto:
Por tanto cuando se utiliza el operador en solitario en una sentencia, como "amas++", no
importa la modalidad escogida. Si importa, y mucho, cuando el operador y su operando forman parte
de una expresión mayor, en un caso como este, uno debe tener bastante claro el resultado que desea
obtener.
talla = 29.0;
while (++talla < 48.5)
{
pie=ESCALA*talla+tope;
printf("%10.1f %16.2f cm. \n", talla, pie);
}
Primero se aumenta en uno el valor de talla y se compara con 48.5. Si es menor, el programa se
introduce en el bucle, ejecutándose este una vez. Vuelta a la sentencia while, nuevo incremento en uno
de talla y nueva comparación. El ciclo se repite hasta que talla exceda el valor prefijado. Nótese que la
primera vez talla se incrementa y toma el valor 30.0. Si hubiésemos usado la forma sufijo, talla++,
talla se incrementaría después de realizada la comparación.
/* -----------------
Programa incre3.c
----------------- */
#define MAX 15
main()
{
int cont = 0;
printf("\n\n ()()()()()()()()()()()()()()()()()() \n");
printf(" Contare ovejas para dormirme. \n");
printf(" ()()()()()()()()()()()()()()()()()() \n\n");
while (++cont<MAX)
printf(" %d millones de ovejas y aun no me he dormido ...\n",cont);
printf("\n\n %d millones de ovejas y ZZZzzzzzz ...... \n",cont);
}
4.2.2.-OPERADOR DECREMENTO.
Existen también en "C" dos operadores decremento que se corresponden con los incremento
que acabamos de comentar. En ellos se utiliza -- en lugar de ++.
Los operadores de incremento y decremento tienen una precedencia o asociación muy alta; Por
ello x*y++ significa (x)*(y++) y no (x*y)++; por otra parte no puede ser de otra forma, ya que esta
última expresión carece de sentido, por que los operadores de incremento y decremento afectan a una
variable, y el producto x*y no los es.
char ch;
int i;
float f;
double d;
result=(ch / i) + (f * d) - (f + i);
double
double
Se puede forzar a una expresión a ser de un tipo específico usando la construcción llamada
"cast"
(tipo)expresión
donde tipo es uno de los tipos estándares de datos de C. Por ejemplo, si "x" es un entero y se quiere
uno asegurar que la computadora evaluaría la expresión "x/2" como de tipo float para garantizar un
resultado float haremos:
(float) x/2;
Aquí el cast (float) se asocia con x, que provoca que la computadora eleve 2 al tipo float y el
resultado a float. Nótese la diferencia si el caso anterior lo escribimos como
(float) (x/2);
donde la computadora obtiene una división entera y eleva el resultado a tipo float.
Los "cast" son, con frecuencia, considerados como operadores. Como operador, un cast es
unario y tiene la misma precedencia que cualquier operador unario.
Vemos un ejemplo.
/* -----------------
Programa: cast1.c
----------------- */
#include <stdio.h>
main()
{
int i;
for (i=1;i<=10;i++)
printf(" %d/2 es : %2.2f \n",i,(float) i/2);
}
4.4.-OPERADORES BINARIOS.
Una característica del lenguaje "C" es la posibilidad de manipular los bits de los datos. Ello se
consigue empleando operadores binarios.
Operador Símbolo
-------------- ---------------
AND &
OR |
XOR ^
NOT ~
desplazamiento derecho >>
desplazamiento izquierdo <<
Los operadores binarios sólo pueden aplicarse a datos de los tipos char, unsigned char, int y
unsigned int.
Los bits de un carácter entero se numeran de derecha a izquierda y de 0 a "n-1" con objeto de
referenciarlos en las operaciones.
Carácter
7 6 5 4 3 2 1 0
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Los operadores AND, OR y XOR requieren dos operandos y producen un tercero (resultado);
en cambio NOT y los desplazamientos sólo requieren un operando para producir el resultado.
Los formatos de los operadores intersección (AND), unión (OR) y unión excluyente (XOR)
son respectivamente.
donde los operadores op1 y op2 se escriben en notación hexadecimal correspondiendo a datos del
mismo tipo.
La siguiente tabla proporciona las reglas para obtener los resultados de los operadores bit a bit.
/* --------------
Programa and.c
-------------- */
#define VERDAD 1
#define BORRA_P "\x1B[2J" /* borra la pantalla */
#define INICIO "\x1B[1;1f" /* cursor a la esquina superior izquierda "
*/
#define FINAL "\x1B[25;1f" /* cursor a la esquina inferior izquierda " */
main()
{
unsigned char op1,op2;
printf(BORRA_P);
printf(FINAL);
printf("... Ejemplo de la función logica binaria & ...");
printf(INICIO);
printf("\nIntroduzca dos numeros hexadecimales de dos digitos
(separados por coma):");
scanf("%x,%x",&op1,&op2);
printf("%02x & %02x = %02x\n",op1,op2,op1&op2);
printf(FINAL);
}
dato & 2n
Ejemplos:
hexadecimal 41 & 10 = 0
decimal 65 & 16(24) = 0
binario 0 1 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘
es 0 (falso).
Análogamente el bit 4 del carácter '<' (ASCII 3C hex.) es 1 porque el resultado de la operación.
hexadecimal 3C & 10 = 10
El operador XOR se utiliza para cambiar un bit, cuya posición dentro de un dato es conocida:
si es 0 se cambia a 1 y si es 1 se cambia a 0. Ello se consigue con la operación
dato ^2n
Ejemplo:
hexadecimal A5 ^ 10 = B5
decimal 165 ^ 16(24) = 181
binario 1 0 1 0 0 1 0 1 0 0 0 1 0 0 0 0 1 0 1 1 0 1 0 1
└─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘ └─┴─┴─┴─┴─┴─┴─┴─┘
Estos operadores son monarios (sólo un operando) y sus formatos son, respectivamente
El operador "desplazamiento a la derecha" mueve a la derecha cada bit del operando el número
de lugares especificado en la operación. El resultado depende del tipo de datos del operando op.
Si el dato tiene uno de los tipo unsigned char o unsigned int, se insertan tantos ceros a la
izquierda como lugares desplazados. Pero si el dato tiene signo, es decir se de uno de los tipos char o
int, entonces cada vez que se desplaza un bit a la derecha pone el signo (0 si es positivo o 1 si es
negativo) en el bit situado más a la izquierda.
Ejemplos:
es, se declara unsigned char, el resultado es 18 (hex.) mientras que si se considera con signo el
resultado es f18 (hex.).
CAPÍTULO 5
SENTENCIAS “C”
5.1.-INTRODUCCION.
Las sentencias de un programa "C" controlan la forma en que se ejecuta dicho programa. Toda
sentencia "C" está formada por palabras clave, expresiones y otras sentencias. Las palabras clave son
las siguientes:
break do if
case else return
continue for switch
default goto while
Una sentencia puede ser una "sentencia compuesta" si esta formada por una o más sentencias;
en este caso estará encerrada entre corchetes "{}".
5.2.-SENTENCIAS SECUENCIALES.
Formato:
{
[declaración]
.
.
sentencia
[sentencia]
.
.
}
Ejemplo:
if (i>0)
{
línea[i]=x;
x++;
i--;
}
5.3.-SENTENCIA IF.
Formato:
if (expresión)
sentencia(s)
[else
sentencia(s)]
Ejemplo:
if (i>0)
y=y/i;
else
{
x=i;
y=f(x);
}
"C" no dispone de una sentencia "else if" pero el mismo efecto se consigue anidando varias
sentencias "if".
Cuando se anidan varias sentencias "if" lo mejor es usar llaves para agruparlas y conseguir
mayor claridad; en el caso de que no se utilicen el compilador asocia cada "else" con el "if" más
reciente que no tenga "else", siempre y cuando no existan llaves que indiquen lo contrario.
Ejemplo:
En el primer ejemplo, el "else" se asocia con la sentencia "if" más interna. Por tanto si "i" es
menor o igual a 0 a la variable "x" no se le asocia ningún valor.
En el segundo ejemplo las llaves rodeando el segundo "if" hacen que el "else" sea asociado por
el compilador al primer "if". Por ello si i es menor o igual a 0 la variable "x" toma el valor de "i".
Toda expresión en "C" tiene un valor. Esta afirmación es cierta incluso para expresiones de
relación, tal como se demuestra en el siguiente ejemplo:
/* ------------------
Programa: cierto.c
------------------ */
main()
{
int cierto, falso;
cierto = (10 > 2);
falso = (10 == 2);
printf("\n\n Cierto = <%d> ; Falso = <%d> \n\n",
cierto,falso);
}
De la ejecución de este programa se deduce que el valor cierto para el lenguaje "C" es 1, y el
valor falso es 0.
/* ----------------
Programa: test.c
---------------- */
main()
{
printf("\n\n");
if (1)
printf(" 1 -> Significa cierto. \n");
else
printf(" 1 -> Significa falso. ");
if (0)
printf(" 0 -> Significa cierto. ");
else
printf(" 0 -> Significa falso. \n\n");
}
Comprobamos en el tercer ejemplo inferior como "C" toma cualquier valor distinto de 0 como
"cierto" y que únicamente se toma como "falso" el valor 0.
/* -----------------
Programa: test2.c
----------------- */
main()
{
printf("\n\n");
if (300)
printf(" 300 es cierto. \n");
if (-55)
printf(" -55 es cierto. \n\n";
}
De esto deducimos que el "C" tiene una noción de "cierto" muy tolerante, que es aprovechada
por muchos programadores. Por ejemplo las dos siguientes frases son equivalentes:
if (numero != 0) if (numero)
/* ------------------
Programa: verdad.c
------------------ */
main()
{
char resul;
printf(" -----------\n");
printf("Expresión: !-3==&&7>10\n");
printf(" -----------\n");
printf("Valor:");
resul=(!(-3==0&&7>10))?'V':'F';
if (resul=='V')
printf (" verdadera \n\n");
else
printf(" falsa");
}
/* -----------------
Programa: falso.c
----------------- */
main()
{
char resul;
printf("\n\n\n");
printf(" -------------------\n");
printf(" Expresión: ( !-3==0&&7>10 )\n");
printf(" --------------------\n");
printf("\n\n Valor:-> ");
resul=(!-3==0&&7>10)?'V':'F';
if (resul=='V')
printf (" verdadera");
else
printf(" falsa\n\n");
}
Ejemplo3: Programa que realiza conversiones entre los distintos sistemas de numeración.
/* ---------------------
Programa: impresion.c
--------------------- */
#include <stdio.h>
main()
{
int opcion;
int valor;
printf("\n\n\n Conversión \n");
printf(" ---------- \n\n");
printf(" 1: decimal a hexadecimal \n");
printf(" 2: hexadecimal a decimal \n");
printf(" 3: decimal a octal \n");
printf(" 4: octal a decimal \n\n");
printf(" introduzca opcion --> ");
scanf("%d",&opcion);
if (opcion==1)
{
printf("\n\n\n");
printf(" Introduzca un valor decimal: ");
scanf("%d",&valor);
printf("\n\n %d en decimal \n\n en hexadecimal es:%x" ,valor,valor);
printf("\n\n\n");
}
if (opcion==2)
{
printf("\n\n\n");
printf(" Introduzca un valor hexadecimal: ");
scanf("%x",&valor);
printf("\n\n %x en hexadecimal \n\n en decimal es:%d ,valor,valor);
printf("\n\n\n");
}
if (opcion==3)
{
printf("\n\n\n");
printf(" Introduzca un valor decimal: ");
scanf("%d",&valor);
printf("\n\n %d en decimal \n\n en octal es:%o" ,valor,valor);
printf("\n\n\n");
}
if (opcion==4)
{
printf("\n\n\n");
printf(" Introduzca un valor octal: ");
scanf("%o",&valor);
printf("\n\n %o en octal \n\n en decimal es:%d" ,valor,valor);
printf("\n\n\n");
}
}
5.4.-SENTENCIA BREAK.
Formato:
Break;
La sentencia break termina la ejecución del do, for, switch o while más interno, pasando el
control fuera del cuerpo de dichas sentencias. Si una sentencia break aparece fuera de alguna sentencia
do, for, switch, o while se producen error.
Ejemplo:
for (i=0:i<LARGO-1;i++)
{
for (j=0;j<ANCHO-1;J++)
{
if (lineas[i][j] == '\0')
{
longitud[i]=j;
break;
}
}
}
En este ejemplo la sentencia break provoca un abandono del cuerpo del segundo for después de
que se encuentra el carácter de terminación null ('\0') por cada cadena, cuya longitud se almacena en
longitud[i]. El control por tanto pasa al primer for, donde la variable i se incrementa y el proceso se
repite hasta que i sea mayor o igual a LARGO-1.
5.5.-SENTENCIA CONTINUE.
Esta sentencia pasa el control a la siguiente iteración de una sentencia for, do o while.
Dentro de una sentencia do, for o while la siguiente iteración comienza analizando de nuevo la
expresión asociada al do, for o al while, y si esta es cierta provoca otra iteración en caso contrario
abandona el bucle.
Ejemplo:
while (i-->0)
{
x=altura(i);
if (x==1)
continue;
y=x*x;
}
El cuerpo de la sentencia se ejecuta si la variable "i" es mayor que 0. Primero se asigna el valor
de ejecutar la función altura() con el valor "i" a "x", luego si se cumple que x toma valor 1 se ejecuta la
sentencia continue con lo cual el resto de las sentencias del bloque son ignoradas y el control pasa al
inicio del bucle volviéndose a analizar la expresión "i-->0".
5.6.-SENTENCIA SWITCH.
Formato:
switch (expresión)
{
[declaración]
.
.
[case constante_expresión:]
sentencia
.
.
[case constante_expresión:]
.
.
[sentencia]
.
.
[default:
sentencia]
}
La sentencia "switch" transfiere el control a la(s) sentencia(s) que forma(n) uno de sus bloques.
Esta(s) sentencia(s) cumplen que la constante_expresión de su "case" es igual al valor de la expresión
entre paréntesis. La ejecución de las sentencias comienza en la(s) sentencia(s) seleccionadas y
continua hasta el final del cuerpo o hasta que sea transferido el control fuera de cuerpo con una
sentencia break.
La expresión de la "switch" ha de ser un valor integral o un valor enum. Los valores de los
"case" han de ser únicos.
Las etiquetas de un switch deben de ser constantes de tipo entero (incluyendo char) o bien
expresiones que contengan únicamente constantes. En ningún caso se pueden emplear variables en las
etiquetas.
Ejemplos:
En el primer ejemplo, los tres casos de la "switch" son ejecutados en el caso de que "c" tome
valor "A". Si "c" toma el valor "a" se incrementa en 1 el valor de letra y total, mientras que solo se
incrementa el valor de total cuando c toma un valor distinto de "A" y "a".
En el segundo ejemplo cada caso finaliza con una sentencia "break" que fuerza a salir de la
sentencia "switch" una vez ejecutadas las sentencias de cada caso.
Varios "case" pueden asociarse a una misma sentencia como en el siguiente ejemplo:
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f': función(letra);
/* -------------------
Programa: switch1.c
------------------- */
/*
.. ..
Programa que refleja la opcionalidad
de la sentencia break en switch.
.. .. */
main()
{
int t;
printf("\n\n\n");
for (t=0;t<10;t++)
switch(t)
{
case 1:
printf("Ahora");
break;
case 2:
printf(" es ");
case 3:
printf("la");
printf(" hora de los buenos hombres.\n");
break;
case 5:
case 6:
printf("a ");
break;
case 7:
case 8:
case 9:
printf(".");
}
printf("\n\n\n");
}
Ejemplo2: Programa conversor entre sistemas de numeración resuelto con una sentencia switch.
/* -------------------
Programa: conver2.c
------------------- */
/*
.. ..
Programa conversor de numeros
usando la sentencia switch
1: decimal a hexadecimal
2: hexadecimal a decimaln
3: decimal a octal
4: octal a decimal
.. ..
*/
#include <stdio.h>
main()
{
int opcion;
int valor;
printf("Conversión:\n");
printf(" 1: decimal a hexadecimal \n");
printf(" 2: hexadecimal a decimal \n");
printf(" 3: decimal a octal \n");
printf(" 4: octal a decimal \n");
printf(" Introduzca una opcion : ");
scanf("%d", &opcion);
switch(opcion)
{
case 1:
printf("\n\n\n");
printf(" introduzca un valor decimal: ");
scanf("%d",&valor);
printf(" %d en hexadecimal es :%x",valor,valor);
printf("\n\n\n");
break;
case 2:
printf("\n\n\n");
printf(" introduzca un valor hexadecimal: ");
scanf("%x",&valor);
printf(" %x en decimal es : %d",valor,valor);
printf("\n\n\n");
break;
case 3:
printf("\n\n\n");
printf(" introduzca un valor decimal: ");
scanf("%d",&valor);
printf(" %d en octal es : %o",valor,valor);
printf("\n\n\n");
break;
case 4:
printf("\n\n\n");
printf(" introduzca un valor octal: ");
scanf("%o",&valor);
printf(" %o en decimal es : %d",valor,valor);
printf("\n\n\n");
break;
default:
printf("\n\n\n");
printf(" SELECCION NO VALIDA, repita de nuevo....");
printf("\n\n\n");
break;
}
}
5.7.-SENTENCIA DO.
Sigue el formato:
do
sentencia(s)
while (expresión);
El cuerpo de una sentencia do se ejecuta una o más veces hasta que la "expresión" pase a ser
falsa. Primero se ejecutan las sentencias del bloque, luego se evalúa la expresión; si esta es falsa
(cero), la sentencia do termina y el control pasa a la siguiente sentencia a continuación del while. Si la
expresión es cierta (distinta de cero) se ejecuta de nuevo el cuerpo del bucle.
La sentencia do puede también finalizar con la ejecución de una sentencia break,goto, o return
dentro del cuerpo de dicho bucle.
Ejemplo:
x=5
do
{
y=altura(i);
x--;
}
while (x > 0);
/* ---------------
Programa: do1.c
--------------- */
main()
{
int opcion, valor;
do
{
printf("\n\n");
printf(" 1.- Calculo del cuadrado. \n");
printf(" 2.- Calculo del cubo.\n");
printf("\n Seleccione opcion: ");
scanf("%d",&opcion);
}
while (opcion<1 || opcion>2);
printf("\n\n Introduzca un valor decimal: ");
scanf("%d",&valor);
switch(opcion)
{
case 1:
printf(" Su cuadrado es : %d",valor*valor);
break;
case 2:
printf(" Su cubo es : %d",valor*valor*valor);
break;
}
printf("\n\n");
}
5.8.-SENTENCIA WHILE.
Formato:
while (expresión)
sentencia(s)
El cuerpo de la sentencia while se ejecuta 0 o más veces mientras la expresión sea cierta.
Primero se evalúa la expresión, si inicialmente es falsa (cero), el cuerpo de while no se ejecuta y el
control pasa a la siguiente sentencia del programa a continuación del while. Si la expresión es cierta
(distinto de 0) el cuerpo del while es ejecutado y a continuación se vuelve a evaluar la expresión y así
sucesivamente se ejecutará el cuerpo del bucle mientras la expresión sea cierta.
Una sentencia while puede también finalizar con la ejecución de una sentencia "break", "goto"
o "return" dentro del cuerpo del bucle.
Ejemplo:
while (i >= 0)
{
cadena[i]=cadena2[i];
i--;
}
El ejemplo superior copia todos los caracteres de cadena2 a cadena1. Si i se mayor o igual a
cero cadena2[i] se asigna a cadena1[i] y el valor de i es decrementado en una unidad. Cuando i toma el
valor -1 termina la ejecución de la sentencia while.
/* ------------------
Programa: while1.c
------------------ */
/*
.. ..
Este programa convierte numeros de zapatos
a cm. de pie.
.. .. */
main()
{
float zapato,pie;
while (zapato<48.5)
{
pie=ESCALA*zapato+TOPE;
printf("%10.1f %16.2f cm \n",zapato,pie);
zapato = zapato + 1.0;
}
printf("\n Nota: Ud. sabe donde le aprieta su zapato. \n");
}
/* ------------------
Programa: while2.c
------------------ */
main()
{
int cont, suma;
cont = 0;
suma = 0;
printf("\n\n\n");
while (cont++ < 10)
suma = suma + cont;
printf("La suma de los %d primeros numeros es = %d \n"
,10,suma);
printf("\n\n\n");
}
/* ------------------
Programa: while3.c
------------------ */
#define MAX 5
main()
{
int cont = MAX + 1;
5.9.-SENTENCIA FOR.
Formato:
for ([expr_inicial];[expr_condicion];[expr_bucle])
sentencia(s);
El cuerpo de una sentencia for se ejecuta 0 o más veces mientras que "expr_condición" sea
cierta. "expr_inicial" y "expr_bucle" son opcionales y se usan para inicializar y modificar los valores
de las variables durante la ejecución de la sentencia for.
1. Si esta expresión es cierta (distinta de 0), se ejecuta el cuerpo de la sentencia for; luego se
evalúa expr_bucle, si esta presente, comenzando el proceso de nuevo evaluando
expr_condicion.
Una sentencia for también termina si dentro del cuerpo de esta se ejecuta una sentencia for,
break o return.
Ejemplo:
for (i=espacio=tabulado=0;i<MAYOR;i++)
{
if (línea[i] == '\x20')
espacio++;
if (línea[i]=='\t')
{
tabulado++;
línea[i]='\0x20';
}
}
Este bucle cuenta el número de caracteres espacio (\0x20) y tabulado (\t) en un array de
caracteres de nombre línea, reemplazando cada tabulado con un carácter espacio.
/* ----------------
Programa: for1.c
---------------- */
main()
{
int i;
printf("\n\n");
for(i=0;i<5;i++)
{
printf(" Esto es i : %d",i);
printf(" y su cuadrado es : %d \n",i*i);
printf("\n");
}
printf("\n\n");
}
/* ----------------
Programa: for2.c
---------------- */
main()
{
int x;
for(x=1;x<=100;x++)
printf(" - %d",x);
printf(" -\n");
/* ----------------
Programa: for3.c
---------------- */
main()
{
int x;
for(x=100;x>=0;x--)
printf(" - %d",x);
printf(" -\n");
/* -------------------
Programa: impares.c
------------------- */
#include <stdio.h>
main()
{
int i;
for(i=1;i<=100;i++)
if (i%2) printf("- %d ",i);
printf("\n\n");
}
Formato:
goto nombre;
.
.
.
nombre: sentencia(s)
Ejemplo:
if (codigo_error>0)
goto salir;
.
.
.
salir:
return(codigo_error);
5.11.-SENTENCIA RETURN.
Formato:
return[expresión];
main()
{
.
.
y=cuadrado(x);
dibujar(x,y);
.
.
}
cuadrado(x)
int x;
{
return(x*x);
}
void dibujar(x,y)
int x,y;
{
.
.
return;
}
Los paréntesis de la expresión de "return" no son obligatorios, se suelen colocar por convenio.
EJEMPLOS RECAPITULATIVOS:
/* ------------------
Programa: divide.c
------------------ */
#include <stdio.h>
main()
{
int a,b;
printf("\n\n\n");
printf("Introducir dos numeros (separados por un espacio) :");
scanf("%d %d",&a,&b);
printf("\n\n");
if(b)
printf("<%d> division entera <%d> =-> <%d>\n\n",a,b,a/b);
else
printf(" no puedo dividir por cero \n\n");
}
Ejemplo2: Una versión del clásico juego de adivinar un número, en el que el ordenador hace de
adivino. Como práctica modificar el programa para que sea el ordenador el que piense
el número.
/* -------------------
Programa: adivina.c
------------------- */
# include <stdio.h>
# define ALTO 100
# define BAJO 1
main()
{
int sup = (ALTO + BAJO)/2;
int max = ALTO;
int min = BAJO;
char respuesta;
printf(" Escoja un numero entre %d y %d. Tratare",ALTO,BAJO);
printf(" de adivinarlo. \n Responda <s> si he acertado,
<a> si mi");
printf(" numero es demasiado alto. \n <b> si demasiado");
printf(" bajo. \n\n");
printf("(============================================) \n");
printf("( Hmmm ........... su numero es el -> %3d ) \n", sup);
printf("(============================================) \n\n");
while ((respuesta = getchar()) != 's')
{
if (respuesta != '\n')
{
if (respuesta == 'a')
{
max = sup-1;
sup = (max +min)/2;
printf("(============================================) \n");
printf("( Demasiado alto .... Entonces sera -> %3d) \n", sup);
printf("(============================================) \n\n");
}
else if (respuesta == 'b')
{
min = sup + 1;
sup = (max + min)/2;
printf("(============================================) \n");
printf("( Demasiado bajo .... Entonces sera -> %3d )\n", sup);
printf("(============================================) \n\n");
}
else
{
printf("\n");
printf(" ......................................... \n");
printf(" . No comprendo; Utiliza una <s>o una <a>.\n");
printf(" . o una <b> .\n");
printf(" .......................................... \n");
}
}
}
printf("\n\n");
printf(" ..........................................\n");
printf(" . SABIA QUE LO CONSEGUIRIA .\n");
printf(" . FUE FACIL. .\n");
printf(" ..........................................\n");
}
/* -----------------
Programa: Zenon.c
----------------- */
/*
Este programa refleja el problema que se planteo Zenon
con las distancias.
Por otro lado se puede ver la potencia de la sentencia
for en TURBO C.
*/
# define LIMITE 15
main()
{
int cont;
float suma, x;
printf("\n\n\n");
for (suma=0.0, x=1.0, cont=1; cont <= LIMITE; cont++,x*=2.0)
{
suma += 1.0/x;
printf(" suma = %2.4f en la etapa %3d \n",suma,cont);
}
printf("\n\n\n");
}
CAPÍTULO 6
CONSIDERACIONES: ARRAYS, PUNTEROS
Para localizar un elemento determinado dentro del conjunto dimensionado se emplean los
números no negativos 0,1,2, ... llamados 'indices'. Por esta razón se dice que los elementos están
indexados. Dependiendo del número de indices los conjuntos se llaman monodimensionales,
bidimensionales, etc, y en general multidimensionales. El número máximo de índices esta limitado por
el compilador.
Los conjuntos dimensionados se declaran indicando, nombre y tamaño, este último encerrado
entre corchetes. (Repasar capítulo "Variables y Tipos en "C", apartado: Declaración de arrays).
Ejemplo:
int x[6];
float tabla[3][4];
Nota: Se toma como tamaño del tipo int 2 bytes. Máquina de 16 bits.
5 16 40 - 24 13 8
1000 1002 1004 1006 1008 1010
Como el compilador reserva 2 octetos para cada elemento de tipo int, el total de octetos
reservados es:
6 * sizeof(int) = 6 * 2 = 12 octetos.
x[3] = -24
y su dirección
&x[3] = 1006
El compilador reconoce la dirección del primer elemento por el nombre del conjunto, sin
indices ni corchetes:
x == &x[0]
En el segundo ejemplo, la variable tabla se estructura sobre dos índices y permite albergar 12
números de tipo float, agrupados por filas y columnas. Suponiendo que se almacenan a partir de la
dirección 2000.
Columna
--------
0 1 2 3
En este caso el compilador reserva cuatro octetos para cada elementos, con un total de:
Para referenciar un elemento individual del conjunto bidimensional se emplean dos indices,
uno de fila y otro de columna. Así p. ej. el elemento séptimo del conjunto, que está situado en la fila 1
columna 2 de cuadro se representa mediante la variable:
tabla[1][2]==-8.6
&tabla[1][2]==2024
Ahora bien, el lenguaje "C" tiene la capacidad de tratar cada fila del conjunto bidimensional
como un conjunto monodimensional. Esto explica por qué las direcciones de las filas se representan
con el nombre del conjunto seguido del primer índice. Por ej.
&tabla[1][2]=tabla+1*cols*sizeof(float)+2*sizeof(float)
= 2000 + 1 * 4 * 4 + 2 * 4
= 2024
¿Qué ocurre cuando se pasa la dirección de un conjunto dimensionado como argumento de una
función?. El compilador no crea un nuevo conjunto como hace con las variables locales (llamada a la
función por valor) sino que pasa únicamente la dirección del conjunto (llamada por referencia). En
consecuencia, sólo se guarda una copia del conjunto en memoria, independientemente del número de
funciones a que puedan acceder.
Por otro lado, la función que recibe la dirección de un conjunto dimensionado tiene que definir
todas las dimensiones excepto la primera, que puede omitirse; de otra manera no haría la indexación
correctamente.
6.2.-CADENAS.
En lenguaje "C" una cadena no es un tipo especial de dato, como ocurre con otros lenguajes
(BASIC, PASCAL, etc); es simplemente un conjunto dimensionado de tipo char. Cada carácter de la
cadena ocupa un octeto de la memoria y al final de la misma el compilador añade el carácter nulo '\0'
que, en realidad es la única forma que tiene el compilador de saber dónde se encuentra el final de la
cadena.
Ejemplos:
char cad1[11];
char cad2[4][6];
Supondremos que el compilador almacena los conjuntos cad1[] y cad2[] a partir de las
direcciones 1500 y 2500, respectivamente.
cad1
S a n t i a g o \0
└───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
1500 1501 1502 1503 1504 1505 1506 1507 1508 1509
cad2
A l a v a \0
0 └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
2500 2501 2502 2503 2504 2505
C a d i z \0
1 └───┘ └───┘ └───┘ └───┘ └───┘ └───┘
2506 2507 2508 2509 2510 2511
L u g o \0
En el primer caso, se declara que la variable cad1 puede almacenar hasta 10 caracteres más
uno. El nombre cad1 es la dirección del primer carácter.
cad2[3][0] = 'A'
A continuación vemos un programa que lee cadenas por teclado y las imprime en pantalla,
carácter a carácter. Utiliza variables registro.
/* -----------------
Programa: texto.c
----------------- */
# define FIL 100
# define COL 81
# define BORRA_P "\x1B[2J"
# define FINAL "\x1B[25;1f"
# define INICIO "\x1B[1;1f"
char text[FIL][COL];
main()
{
register int i,j,k;
printf(BORRA_P);
printf(FINAL);
printf(" Escriba una línea y pulse <INTRO>. \n");
printf(" Para terminar pulse <INTRO> al principio de una línea.");
printf(INICIO);
for(k=0;k<FIL;k++)
{
printf("%d ", k+1);
gets(text[k]);
if (!text[k][0])
break;
}
printf(BORRA_P);
for(i = 0; i<k ; i++)
{
puts(text[i]);
}
}
El lenguaje "C" también acepta las constantes de cadena y las trata como tales. p. ej. este programa:
/* ------------------
Programa: saludo.c
------------------ */
El siguiente programa inicializa una cadena y luego llama a una función para vaciarla.
/* -----------------
Programa: vacia.c
----------------- */
void vacia(char[]);
main()
{
static char cadena[]="De Marin al cielo";
printf("Cadena inicial: %s. \n", cadena);
vacia(cadena);
printf("\nCadena final: %s \n ", cadena);
}
void vacia(c)
char c[];
{
c[0]='\0'; /* Carácter nulo al principio de la cadena. */
}
6.3.-PUNTEROS.
Un puntero es una variable que contiene una dirección de memoria. Se declara indicando el
tipo de dato que apunta y un nombre, precedido de un asterisco (*). (Ver capítulo "Variables y Tipos
en "C", apartado: Declaración de Punteros).
Ejemplos:
char *puntero;
int *punt_entero;
float *punt_flot[10];
Los punteros permiten hacer relación a las variables de forma indirecta, a través de sus
direcciones.
La razones para utilizar punteros son varias:
A los punteros se les puede sumar o restar constantes. En cambio, no es posible realizar con
ellos operaciones de multiplicación, división ni desplazamiento de bits.
Ejemplo:
ptri = ptri +3
y hace que ptri contenga la dirección 1006. Ténganse en cuenta que ptri apunta a elementos de dos
octetos (bytes).
Los punteros también se pueden comparar entre ellos mediante lo operadores de relación. Se
comparan direcciones, no los contenidos de los campos que apuntan. El programa siguiente compara
punteros a enteros largos.
Ejemplo:
/* -----------------
Programa: punte.c
----------------- */
long x,y;
main()
{
long int *ptrx,*ptry;
ptrx = &x+2;
ptry = &y;
printf("direccion de x .... <%d> \n",&x);
printf("direccion de y .... <%d> \n\n",&y);
if (ptrx < ptry)
printf(" ptrx (=%d) direccion mas baja que ptry (= %d)",ptrx,ptry);
else if (ptrx > ptry)
printf(" ptrx (=%d) direccion mas alta que ptry (= %d)",ptrx,ptry);
else
printf(" ptrx (=%d) direccion igual a ptry (= %d)",ptrx,ptry);
}
float *ptrf;
ptrf=(float *)NULL
proporcionan un puntero nulo a flotante. Nótese que el puntero nulo no contiene una direc-ción, sino el
valor 0. Véase también el uso de un nombre de tipo (float *) para asegurar el tipo de dato, puntero a
float.
A partir de estas sentencias, el acceso a cualquier elemento del conjunto puede hacerse de dos
formas totalmente equivalentes: indexando o con punteros. Así p. ej. el quinto elemento del conjunto
dimensionado se representa:
A pesar de la equivalencia, los programadores se inclinan por una u otra forma en razón de la
optimización en el tiempo. En general, es más rápido el uso de punteros en proceso secuenciales; en
cambio, la indexación es mejor en los procesos de acceso directo.
Cuando se trata con conjuntos bidimensionales, la notación de punteros proporciona una de las
características más interesantes del lenguaje "C": la doble indexación o tratamiento de punteros que
apuntan a punteros. Veamos un ejemplo:
float tabla[3][4];
La dirección del conjunto completo es tabla, que empieza en la dirección 2000. El compilador
interpreta la expresión tabla+1, o su equivalente tabla[1], como la dirección de la segunda fila del
conjunto, y para calcularla hace la suma de tabla con 4*sizeof(float), que es el número de bytes de una
fila completa; por tanto tabla+1 tiene la dirección 2016.
/* --------------------
Programa: matrices.c
-------------------- */
# define FIL 4
# define COL 4
# define BORRA_P "\x1B[2J"
main()
{
int i,j;
static int mat1[FIL][COL]=
{{10,15,17,19},
{20,22,24,26},
{31,33,35,37},
{40,42,44,46}};
static int mat2[FIL][COL]=
{{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}};
int mat3[FIL][COL];
printf(BORRA_P);
printf("\n\n\n\n");
for (i=0;i<FIL;i++)
for (j=0;j<COL;j++)
*(*(mat3+i)+j) = *(*(mat1+i)+j) + *(*(mat2+i)+j);
printf(" < MATRIZ 1 >\t < MATRIZ 2 >\t< MATRIZ SUMA > \n");
printf(" ---------- \t --------- \t ------------- \n\n");
for(i=0;i<FIL;i++)
{
for (j=0;j<COL;j++)
printf("%3d",*(*(mat1+i)+j));
printf(" ");
for (j=0;j<COL;j++)
printf("%3d",*(*(mat2+i)+j));
printf(" ");
for (j=0;j<COL;j++)
printf("%3d",*(*(mat3+i)+j));
printf("\n");
printf("\n\n");
for (i=0;i<FIL;i++)
for (j=0;j<COL;j++)
mat3[i][j] = mat1[i][j] + mat2[i][j];
printf(" < MATRIZ 1 >\t < MATRIZ 2 >\t< MATRIZ SUMA > \n");
printf(" ---------- \t --------- \t ------------- \n\n");
for(i=0;i<FIL;i++)
{
for (j=0;j<COL;j++)
printf("%3d",mat1[i][j]
printf(" ");
for (j=0;j<COL;j++)
printf("%3d",mat2[i][j]
printf(" ");
for (j=0;j<COL;j++)
printf("%3d",mat3[i][j]
printf("\n");
}
}
6.5.-PUNTEROS A CADENAS.
El uso de puntero tiene claras ventajas sobre la indexación cuando se inicializan cadenas, como
veremos en seguida.
Comparemos la sentencia
char *capital="Pontevedra";
puts(capital+2);
A continuación veremos otra diferencia entre ambas versiones. Con la versión indexada la
sentencia
informa al compilador para que almacene en memoria cuatro cadenas de caracteres, todas de tamaño
11, incluyendo el carácter nulo.
también el compilador reserva espacio en memoria para almacenar las cuatro cadenas, sólo que una a
continuación de la otra, sin dejar espacios vacíos, con lo que el ahorro de memoria es de 8 octetos.
versión indexada.
P o n t e v e d r a \0
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
1000 1010
S a n x e n x o \0
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
1011 1021
M a r í n \0
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
1022 1032
V i g o \0
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
1033 1043
P o n t e v e d r a \0
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
1000 1010
S a n x e n x o \0
└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘└───┘
1011 1019
M a r í n \0
└───┘└───┘└───┘└───┘└───┘└───┘
1020 1025
V i g o \0
└───┘└───┘└───┘└───┘└───┘
1026 1030
Veamos un ejemplo:
/* ------------------
Programa: vacia2.c
------------------ */
printf(BORRA_P);
printf(FILA10);
printf("Cadena inicial: <%s>. ", cadena);
vacia(cadena);
printf(FILA12);
printf("Cadena final : <%s>.\n\n\n", cadena);
}
void vacia(cad)
char *cad;
{
*cad='\0'; /* Carácter nulo al principio de la cadena. */
}
El paso de un conjunto dimensionado a una función sigue los mismos criterios que los de
cualquier otro conjunto dimensionado. La función que recibe el conjunto dimensionado tiene que
definir todos los índices excepto el primero, que puede omitirse.
/* ---------------
Programa: imp.c
--------------- */
void imp(int *[], int);
# define BORRA_P "\x1B[2J"
main()
{
int i;
static int primos[]={2,3,5,7};
int *ptri[4];
printf(BORRA_P);
for (i=0;i<4;i++)
ptri[i]=primos+i;
imp(ptri,4);
}
void imp(ptri,n)
int *ptri[], n;
{
int i;
printf("Numeros : %d", *ptri[0]);
for (i=1;i<n;i++)
printf(", %d", *ptri[i]);
printf("\n\n");
printf("Direcciones : ");
for (i=0;i<n;i++)
printf(" %u",ptri[i]);
printf("\n\n");
}
6.6.-PUNTEROS A FUNCIONES.
La dirección de una función es el punto de entrada de la misma. Cuando el montador de
enlaces transforma el código objeto en código ejecutable, define los puntos de entrada de cada
función.
Si un puntero contiene el punto de entrada de una función, puede utilizarse para llamar a la
función.
El compilador reconoce la dirección de una función por el nombre de la misma sin paréntesis
ni argumentos. Es muy parecido al criterio que sigue para determinar la dirección de un conjunto
dimensionado, con el nombre sin corchetes ni índices.
El siguiente programa muestra cómo se puede asignar la dirección de una función a un puntero
y hacer la llamada con él, sin utilizar el nombre de la función.
/* ---------------
Programa: max.c
--------------- */
# define BORRA_P "\x1B[2J"
# define FILA10 "\x1B[10;10f"
# define FILA13 "\x1B[13;10f"
int max(x,y)
int x, y;
{
int resul;
resul=(x>y)?x:y;
return(resul);
}
CAPÍTULO 7
FUNCIONES “C”
7.1.-INTRODUCCIÓN.
Una función es una colección independiente de declaraciones y sentencias. Los programas "C"
tienen al menos una función principal, la función main(). Este capitulo describirá como definir,
declarar y llamar a las funciones C.
Una definición de una función especifica el nombre de la función, sus parametros formales y
las declaraciones y sentencias que definen su acción. La definición de la función puede también llevar
el tipo retornado por esta y su clase de almacenamiento.
Una llamada a una función pasa el control de ejecución de la llamada de la función a la función
llamada.
/* --------------------
Programa: funcion1.c
-------------------- */
# include <stdio.h>
# define BORRA_P "\x1B[2J"
int sqr(int);
main()
{
int t = 10;
printf(BORRA_P);
printf("\n\n\n");
printf(" El valor %d es el cuadrado de %d", sqr(t), t);
printf("\n\n");
}
/* Función cuadrado */
/* ---------------- */
sqr(x)
int x;
{
x = x*x;
return(x);
}
Ejemplo2: Un caso de múltiples llamadas a dos funciones dentro de una sentencia "for".
/* --------------------
Programa: funcion2.c
-------------------- */
/*
.. ..
Suma los valores desde el 0 al 9
.. .. */
#include <stdio.h>
#define BORRA_P "\x1B[2J"
main()
{
int contador; /* variable local */
printf(BORRA_P);
sum=0; /* inicializa la variable global */
for (contador=0;contador<10;contador++)
{
total(contador);
display();
}
}
/* función de visualizacion */
/*---------------------------*/
display()
{
int contador; /* Este contador es distinto al de main() (Es local) */
for(contador=0;contador<10;contador++) printf(".");
printf("La suma actual es %d \n", sum);
}
/* --------------------
Programa: funcion3.c
------------------- */
/*
.. ..
Suma los numeros del 0 a un numero dado.
.. ..
*/
#include<stdio.h>
#define BORRA_P "\x1B[2J"
main()
{
int t;
printf(BORRA_P);
/* Función total */
/*---------------*/
total(x)
int x;
{
int sum=0,i,contador;
for (i=0;i<x;i++)
{
sum=sum+i;
for(contador=0;contador<10;contador++) printf(".");
printf("la suma actual es %d \n",sum);
}
}
7.2.-DEFINICION DE FUNCIONES.
La definición de una función consiste en especificar su nombre, los parametros formales, y el
cuerpo de la función. Se puede definir también el tipo retornado por la función y su clase de
almacenamiento. Sigue el siguiente formato:
[ind_almac.][ind_tipo]declarador([lista_parametros])
[declaración_parametros]
cuerpo_función
Donde:
7.2.1.-CLASES DE ALMACENAMIENTO.
Las clases de almacenamiento especificadas en la definicion de una función pueden ser: static
o extern. Una función con almacenamiento static es visible solo en el fichero fuente en el cual esta
7.2.2.-TIPOS RETORNADOS.
El tipo retornado por una función define el tamaño y tipo del valor retornado por la función. La
declaración del tipo tiene el formato:
[ind_tipo]declarador
donde ind_tipo y declarador definen el tipo y el nombre del valor retornado por la función. Si
no se indica ind_tipo se toma por defecto tipo int.
El indi_tipo puede especificar cualquier tipo fundamentas, estructura o unión. Las funciones no
pueden retornar arrays o funciones, pero pueden retornar punteros a cualquier tipo, incluyendo arrays
y funciones.
Una función retorna un valor cuando una sentencia return que contiene una expresión se
ejecuta, la expresión se evalúa, se convierte, si es necesario, al tipo de valor a retornar y se retorna al
punto de llamada. Si una sentencia return no es ejecutada o si la sentencia return no contiene una
expresión, el valor retornado por la función esta indefinido.
Ejemplo:
1:
static sumar(x,y)
int x,y;
{
return(x+y);
}
2:
typedef struct
{
char nombre[20];
int id;
long clase;
} ESTUDIANTE;
ESTUDIANTE ordenaestu(a,b)
ESTUDIANTE a,b;
{
return((a.id<b.id)?a:b);
}
3:
char *cadenapeque(s1,s2)
char s1[],s2[];
{
int i;
i=0;
while(s[i]!='\0'&&s2[i]!='\0')
i++;
if(s1[i]!=='\0')
return(s1)
else
return(s2);
}
En el primer ejemplo, el tipo retornado por la función sumar es el tipo int por defecto. La
función tiene clase de almacenamiento static.
El tercer ejemplo define una función que retorna un puntero a un array de caracteres, la función
lleva dos arrays (strings) como argumentos y retorna un puntero al más corto de las dos cadenas.
7.2.3.-PARÁMETROS FORMALES.
Los parámetros formales son variables que reciben valores pasados a la función por una
llamada a esta. Los parametros formales se declaran como una lista de parametros al comienzo de la
declaración de la función, definiendo el nombre de los parametros y el orden en el cual reciben los
argumentos de la función de llamada.
Formato:
([identificador[,identificador]]...)
La declaración de los parametros define el tipo y tamaño de los valores que se pueden
almacenar en ellos.
Un parámetro formal puede ser de tipo fundamental, estructura, union, puntero, o array; si no
se indica tipo asume tipo int. Un parámetro formal solo puede tener almacenamientos auto o register,
si este no se indica se asume auto. Los parametros formales pueden ser declarados en cualquier orden.
Los identificadores de los parametros formales se usan en el cuerpo de la función para referirse
a los valores pasados a la función.
Ejemplo:
struct estudiante
{
char nombre[20];
int id;
long clase;
struct estudiante *siguiestu
} estudiante;
main()
{
int compañero(struct estudiante*,char*);
.
.
if (compañero(estudiante.siguiestu,estudiante.nombre)>0)
{
.
.
}
}
compañero(r,n)
struct estudiante *r;
char *n;
{
int i=0;
while (r->nombre[i]==n[i])
if (r->nombre[i++]=='\0')
return(r->id);
}
Este ejemplo contiene una declaración de un tipo estructura, una declaración hacia delante de
la función compañero, una llamada a dicha función y la definición de esta.
Nótese que el mismo nombre, estudiante, se puede usar sin conflicto para el nombre de
estructura y para un campo.
7.2.4.-CUERPO DE LA FUNCIÓN.
El cuerpo de la función esta formado por un conjunto de sentencias que definen las acciones a
realizar por la función y puede también contener la declaración de las variables usadas por estas
sentencias.
Una variable declarada en el cuerpo de una función tiene almacenamiento auto a no ser que se
indique otra cosa. Cuando se llama a la función se crea espacio para las variables locales y se ejecuta
una inicialización local a continuación se pasa el control a la primera sentencia del cuerpo de la
función y se ejecuta el cuerpo de la función hasta que se ejecuta una sentencia return o se encuentra el
final de la función; pasando el control al siguiente punto a la llamada.
7.3.-DECLARACIÓN DE FUNCIONES.
Una declaración de una función define el nombre, el tipo retornado y la clase de
almacenamiento de una función dada y puede establecer el tipo de alguno o todos los argumentos de la
función.
Las funciones pueden ser declaradas implícitamente o con declaración hacia adelante.
Una declaración implícita tiene lugar cuando la función se llama sin ser previamente definida o
declarada. El compilador implícitamente declara la función para retornar un tipo int y para tener clase
de almacenamiento extern.
Una declaración hacia adelante establece los atributos de una función, permitiendo la
declaración de la función a ser llamada antes de su definición o al ser llamada desde otro fichero
fuente. Estas declaraciones tienen varios usos; Establecer el tipo retornado por las funciones que
retornan algún tipo excepto el tipo int. Las funciones que no retornan un tipo int no pueden se
llamadas antes de ser declaradas o definidas, ya que sino el compilador asume que dichas funciones
retornan un tipo int.
Las declaraciones hacia adelante se pueden usar para establecer los tipos de argumentos
esperados de la función de llamada.
La declaración hacia adelante se usa también para declarar punteros a funciones antes de que la
función sea definida.
Ejemplo:
main()
{
int a=0,b=1;
float x=2.0,y=3.0;
double sumareal(double,double);
a=sumaetera(a,b);
x=sumareal(x,y);
}
sumaentera(a,b);
int a,b;
{
return(a+b);
}
double sumareal(x,y)
double x,y;
{
return(x+y);
}
En este ejemplo, la función sumaentera() se declara de forma implícita para retornar un valor
entero, de modo que es llamada antes de su definición.
7.4.-LLAMADAS A FUNCIONES.
7.4.1.-ARGUMENTOS ACTUALES.
Un argumento actual puede ser un valor de tipo fundamental, estructura, union o puntero.
Todos los argumentos actuales son pasados por valor. Una copia de los argumentos actuales se
asigna a los parámetros formales. La función usa esta copia sin tener efecto sobre los argumentos de
llamada.
Los punteros proporcionan un modo de acceder a los valores por referencia desde una función.
Un puntero a una variable almacena la dirección de esta y la función puede utilizar esta dirección para
acceder a arrays y funciones.
Ejemplo:
main()
{
void intercambia(int *, int *);
int x,y;
.
.
intercambia(&x,&y);
void intercambia(a,b)
int *a,*b;
{
int t;
t=*a;
*a=*b;
*b=t;
En este ejemplo, la función intercambia() se declara en la función main() con dos argumentos,
ambos puntero a enteros. Los parámetros formales a y b se declaran como punteros a variables enteras.
/* --------------------
Programa: funcion4.c
-------------------- */
/*
.. ..
Contiene el programa principal para
calcular la superficie de una esfera.
Utiliza la función área() del fichero "sup.c".
.. ..
*/
#include "sup.c"
#define BORRA_P "\x1B[2J"
main()
{
printf(BORRA_P);
printf("\n\n\n");
printf(" Introduzca el radio de la esfera: ");
scanf("%f",&radio);
printf("\n\n -----------------------------------------");
printf("\n\n El área de la esfera es %.10le",área());
printf("\n\n -----------------------------------------\n");
/* -----------------------
Programa: sup.c
Invocado por funcion5.c
----------------------- */
/*
.. ..
Contiene la función área() para calcular el área de una esfera.
la llama el programa del fichero CALC.C
.. ..
*/
{
extern float radio; /* declara la variable externa 'radio' */
return(4*3.1416*radio*radio);
}
# cc -compat función.c
funcion5.c
# ./a.out
-----------------------------------------
-----------------------------------------
Para llamar a una función con un número variable de argumentos, el programador simplemente
indica estos en la función de llamada. En la declaración hacia adelante de una función (si hay alguna),
el número variable de argumentos se especifica colocando una coma al final de la lista de tipos. Un
argumento debe de figurar en la función de llamada por cada nombre de tipo especificado en la lista de
tipos de argumentos. Si solo se indica una coma indica que no se requieren argumentos cuando la
función se llama.
Todos los argumentos dados en la función de llamada se almacena en una pila. El número de
parámetros formales declarado por la función determina como algunos de los argumentos se llevan de
la pila y se asignan a los parámetros formales. El programador es el responsable de toda esta
operación.
Ejemplo:
main()
{
int calificacion(int,);
int cont,media,i;
.
.
media=calificacion(cont,14,96,82);
.
.
}
calificacion(numero)
int numero;
{
int *ip,total=0,i;
ip=&numero+1;
for (i=1;i<=numero;i++,ip++)
total+=*ip;
if (numero>0)
return(total/numero);
return(-1);
7.4.3.-LLAMADAS RECURSIVAS.
Una función en un programa "C" puede ser llamada de forma recursiva. Por lo tanto una
función podrá llamarse a si misma. El compilador "C" permite cualquier número de llamadas
recursivas a un función. En cada llamada, se reserva espacio de almacenamiento para los parámetros
formales y para las variables auto y register, manteniendo siempre sus valores previos. Las variables
definidas con almacenamiento global se mantienen en cada una de las llamadas recursivas ya que
existen durante todo el tiempo de vida del programa; ya que la referencia a esta variables es a la
misma área de almacenamiento.
Por tanto el limite esta marcado por el entorno ya que al requerirse en cada llamada recursiva
memoria adicional para la pila, demasiadas llamadas recursivas pueden provocar un overflow
(desbordamiento) de la pila.
/* --------------------
Programa: funcion5.c
-------------------- */
/*
.. ..
Este programa muestra el funcionamiento
de la recursividad.
La función main() se invoca a si misma.
.. ..
*/
main()
{
char ch;
printf(BORRA_P);
printf(FILA5);
if (ch != 'Q')
main();
else
printf(" Aja!. eso era una - %c - \n\n",ch);
}
/* --------------------
Programa: funcion6.c
-------------------- */
/*
.. ..
Utiliza las funciones 'factorial_1()'
y 'factorial_2()'.
.. ..
*/
printf(BORRA_P);
printf("\n\n\n");
printf(" Introduzca un numero entero (menor que 34):");
scanf("%lu", &n);
solucion=factorial_1(n); /* solucion recursiva */
printf("\n\n\n Solucion recursiva : %lu! -> %lu \n", n, solucion);
solucion=factorial_2(n); /* solucion no recursiva */
/* Función factorial_1() */
/* Función factorial_2() */
return(solucion);
}
7.5.-EJEMPLOS RECAPITULATIVOS.
Ejemplo1: Visualización del contenido de un array dentro de dos funciones, cada una de ellas con una
notación distinta.
/* --------------------
Programa: funcion7.c
-------------------- */
#include <stdio.h>
#define BORRA_P "\x1B[2J"
main()
{
int t[10],i;
printf(BORRA_P);
/* Función display */
/* ---------------- */
display(num)
int num[10];
{
int i;
/* Función display1 */
/* ---------------- */
display1(num)
int *num;
{
int i;
/* --------------------
Programa: funcion8.c
-------------------- */
/*
.. ..
Este programa intercambia dos numeros.
.. .. */
/* --------------------
Programa: funcion9.c
-------------------- */
/*
.. ..
En este programa se contempla el funcionamiento de una
función pasando valores por referencia.
.. ..
*/
main()
{
int x,y;
printf(BORRA_P);
x=10;
y=20;
printf("\n\n\n");
printf(" Valores iniciales de x e y : <%2d> <%2d> \n",x,y);
swap(&x,&y);
printf(" Valores intercambiados de x e y: <%2d> <%2d> \n",x,y);
/* Función swap */
/* ------------ */
swap(x,y)
int *x, *y;
{
int temp;
temp = *x; /* Guarda el valor de la variable apuntada por x en temp */
/* --------------------
Programa: funcio10.c
-------------------- */
int cont=100;
main()
{
printf(BORRA_P);
func1();
}
/* cuerpo de func1() */
/* ----------------- */
func1()
{
printf("\n\n\n\n\n");
func2();
printf("\n\n");
printf(" cont es %d", cont);
printf("\n\n");
func2();
printf("\n\n\n");
}
/* Cuerpo de func2 */
/* --------------- */
func2()
{
int cont;
printf(" ");
for (cont=1;cont<12;cont++)
printf(".");
}
CAPÍTULO 8
FUNCIONES PARA MANEJOS DE CADENAS
8.1.-FUNCIONES DE CADENA
El lenguaje "C" no tiene operadores para el manejo de cadenas. Por esta razón mediante una
sentencia de asignación no se puede asignar una cadena a una variable tipo char. El siguiente ejemplo
seria por tanto incorrecto en base a la afirmación anterior.
/* -------------------
Programa: cadena1.c
------------------- */
3% cc -compat cadena1.c
cadena1.c
cadena1.c(10) : warning 47: `=' : different levels of indirection
Para imprimir la constante CADENA del programa anterior basta especificarla como
argumento en la función printf().
/* --------------------
Programa: cadena2.c
-------------------- */
main()
{
system("clear");
printf(DIEZ);
printf("(%s)\n\n", CADENA);
}
/* -------------------
Programa: cadena3.c
------------------- */
# include <stdio.h>
# define DIEZ "\x1B[10;10f"
main()
{
char cadena[80];
system("clear");
printf("\n\n\n");
printf(" Introduzca una cadena: ");
gets(cadena);
printf("\n\n");
system("clear");
printf(DIEZ);
printf(" La cadena introducida es : <%s> :",cadena);
printf("\n\n\n");
En C no se pueden comparar dos cadenas con los operadores de relación. Por ello el
lenguaje "C" incorpora un amplio conjunto de funciones de programoteca que permiten comparar y
tratar cadenas. La mayoría de ellas serán tratadas en este capítulo.
La mayor parte de las funciones de cadena tienen definidos sus prototipos y constantes en los
ficheros cabecera string.h y ctype.h.
Esta función copia una cadena en otra. El primer argumento es un puntero a la cadena
receptora y el segundo otro puntero a la cadena origen. Devuelve un puntero a la cadena receptora.
/* -------------------
Programa: cadena4.c
------------------- */
# include <stdio.h>
# include <string.h>
# define CINCO "\x1B[5;10f"
main()
{
char cadena[80];
system("clear");
strcpy(cadena,"Hola como estas");
printf(CINCO);
printf("La cadena introducida es : <%s> :\n\n",cadena);
}
- Función strncpy().
- Función strcat().
Copia una cadena al final de otra. El primer argumento es un puntero a la cadena receptora y el
segundo otro puntero a la cadena origen. Devuelve un puntero a la cadena receptora.
Ejemplo: Combinamos las funciones strcpy() y strcat() para dadas dos cadenas concatenarlas.
/* -------------------
Programa: cadena5.c
------------------- */
# include <stdio.h>
# include <string.h>
# define CINCO "\x1B[5;10f"
main()
{
char cadena1[80],cadena2[80];
system("clear");
strcpy(cadena1,"Hola como estas. ");
strcpy(cadena2,"- Muy bien !");
strcat(cadena1,cadena2);
printf(CINCO);
printf("La cadena resultado es : <%s> :\n\n",cadena1);
}
- Función strncat().
- Función strlen().
/* -------------------
Programa: cadena6.c
------------------- */
# include <stdio.h>
# include <string.h>
# define CINCO "\x1B[5;10f"
main()
{
char cadena1[80];
system("clear");
printf(CINCO);
printf("Introduzca una cadena: ");
gets(cadena1);
system("clear");
printf(CINCO);
printf("La longitud de la cadena <%s> es <%d> " ,cadena1,strlen(cadena1)); ║
printf("\n\n\n");
}
La función strchr() (strrchr()) retorna un puntero a la primera (última) ocurrencia del carácter
"c" en la cadena "s" (Ver formato). El primer argumento es un puntero a la cadena y el segundo el
código ASCII del carácter. Devuelve un puntero a la posición carácter dentro de la cadena. Si no se
encuentra el carácter devuelve un puntero NULL. Vemos un ejemplo:
/* -------------------
Programa: cadena7.c
------------------- */
# include <stdio.h>
# include <string.h>
main()
{
char *cadena = "esta es una cadena";
char *caracter, *caracter2;
system("clear");
caracter=strchr(cadena, 97);
caracter2=strrchr(cadena, 101);
printf("\n\nCadena = \"%s\"", cadena);
printf("\n\ncadena1 = '%c'", *caracter);
printf("\n\ncadena2 = '%c'\n\n", *caracter2);
}
/* .. ..
a -> ascii(97).
e -> ascii(101).
.. .. */
- Función strcmp().
Compara dos cadenas. Los argumentos son, ambos, punteros a las cadenas. Devuelve 0 si las
cadenas son iguales, un número mayor que 0 si cadena1 es mayor que cadena2 o un número menor
que 0 si cadena1 es menor que cadena2.
/* -------------------
Programa: cadena8.c
------------------- */
# include <stdio.h>
# include <string.h>
# define CINCO "\x1B[5;10f"
# define SEIS "\x1B[6;10f"
# define DIEZ "\x1B[10;10f"
main()
{
char cadena1[30];
for(;;)
{
system("clear");
printf(CINCO);
printf("Introduzca una cadena. Acabar -> \"salir\" : ");
gets(cadena1);
if (strcmp("salir",cadena1)) visualiza(cadena1);
if(!strcmp("salir",cadena1)) break;
}
system("clear");
printf(CINCO);
printf("Logicamente para salir ha tecleado : <%s> : ",cadena1);
printf(SEIS);
printf("----------------------------------------------");
printf("\n\n\n");
}
visualiza(cadena)
char cadena[30];
{
printf(DIEZ);
printf("<%s>",cadena);
}
- Función strncmp().
Compara los n primeros caracteres de dos cadenas. Los dos primeros argumentos son punteros
a las cadenas, mientras que el tercero es el número n, de tipo int. Devuelve 0 si los n primeros
caracteres de cadena1 son iguales a los de cadena2, un número mayor que 0 si los caracteres de
cadena1 tienen valor mayor que los de cadena2 y un número menor que 0 en caso contrario.
- Función strpbrk().
La función tolower() convierte a minúsculas cualquier carácter del alfabeto inglés. Cualquier
otro carácter no lo convierte.
La función toupper convierte a mayúsculas cualquier carácter del alfabeto inglés. Cualquier
otro carácter no lo convierte.
Ambas funciones devuelven el código del carácter transformado. Vemos un ejemplo del uso de
ambas funciones:
Ejemplo: Un caso interesante, utilizando una función, del uso de estas funciones.
/* -------------------
Programa: cadena9.c
------------------- */
#include "ctype.h"
int conver(void);
main()
{
int minus,mayus;
char tecla;
system("clear");
printf("\n\n Pulse una tecla alfabetica: ");
minus=conver();
printf("\n\n Minuscula -> <%c>", minus);
mayus=toupper(minus);
printf("\n\n");
}
/*-----------*/
int conver()
{
int x;
x = getchar();
x = tolower(x);
if (x==165)
x==164;
return(x);
}
double atof(nptr)
char *nptr;
int atoi(nptr)
char *nptr;
long atol(nptr)
char *nptr;
- Función atof().
Esta función transforma la cadena del argumento en un número de tipo double. El argumento
puede tener signo y utilizar notación exponencial. Si se incluyen caracteres sin interpretación numérica
se da por finalizada la conversión. Esta función devuelve un número transformado o cero a partir de la
subcadena ilegal.
- Función atoi().
Transforma la cadena del argumento en un número de tipo int. El argumento puede tener signo.
Si se incluyen caracteres sin interpretación numérica se da por finalizada la con-versión. Esta función
devuelve el número transformado, o cero si no se puede transformar.
/* ---------------------
Programa: cadena10.c
-------------------- */
main()
{
int valor;
char *cadena="12";
system("clear");
valor = atoi(cadena);
printf("\n\n Cuadrado de valor -> <%d> \n\n", valor * valor);
}
- Función atol().
Transforma la cadena del argumento en un número de tipo int long. Por lo demás, se comporta
igual que atoi(). Esta función devuelve el número transformado, o cero a partir de la subcadena ilegal.
- Función ecvt().
Transforma un número de los tipos float o double en una cadena, con el carácter 'nulo' al final
de la misma. Tiene cuatro argumentos, los dos primeros de entrada y los dos últimos de salida. El
primero contiene el número que se desea convertir; el segundo contiene la longitud de la cadena; el
tercero es un puntero a un entero donde la función almacena un entero que indica la situación del
punto decimal dentro de la cadena por la izquierda (positivo, negativo o cero, según el punto esté
situado a la derecha, a la izquierda o precediendo justamente al primer dígito de la cadena); el cuarto
es otro puntero a un entero donde la función almacena el signo del número (0 si es positivo y 1 si es
negativo). Devuelve un puntero a la cadena transformada. El ejemplo cadena11 es una muestra del uso
de esta función.
/* --------------------
Programa: cadena11.c
-------------------- */
main()
{
char *cadena;
double valor = 3.141592654;
int num_cifras = 10;
int posicion_punto = 1;
int signo = 0;
system("clear");
printf("\n\nLa cadena es: <%s> ",ecvt(3.141592654, num_cifras,
&posicion_punto, &signo));
printf("\n\nNumero de cifras almacenadas : <%d> ",num_cifras);
printf("\n\nPosicion del punto decimal ( primera es 0) : <%d> ",
posicion_punto);
printf("\n\nSigno (positivo 0, negativo 1) : <%d> \n\n", signo);
}
Ejemplo1: Aplicación de la función strlen() en un bucle "for" para inicializar la variable utilizada en
la condición a evaluar.
/* --------------------
Programa: cadena12.c
-------------------- */
/* .. ..
Recordar que las cadenas en C son arrays de caracteres,
terminados con un caracter nulo '\0'.
.. .. */
# include <stdio.h>
# include <string.h>
# define CINCO "\x1B[5;10f"
main()
{
char cadena1[80];
int i;
system("clear");
printf(CINCO);
printf("Introduzca una cadena: ");
gets(cadena1);
system("clear");
printf(CINCO);
for(i=strlen(cadena1)-1;i>=0;i--)
printf("* %c ",cadena1[i]);
printf("*");
printf("\n\n\n");
}
Ejemplo2: Utilizamos la función strcmp() en la condición de una sentencia "if" para com-parar dos
cadenas, indicando si son iguales; Posteriormente las concatenamos con strcat().
/* --------------------
Programa: cadena13.c
-------------------- */
# include <stdio.h>
# include <string.h>
# define CINCO "\x1B[10;5f"
# define ONCE "\x1B[11;30f"
# define DIEZ "\x1B[10;10f"
# define DOCE "\x1B[12;10f"
# define CATO "\x1B[14;10f"
main()
{
char s1[80],s2[80];
system("clear");
printf(CINCO);
printf("Introduzca dos cadenas : ");
gets(s1);
printf(ONCE);
gets(s2);
system("clear");
printf(DIEZ);
printf("Longitud: %d %d \n", strlen(s1),strlen(s2));
if(!strcmp(s1,s2))
{
printf(DOCE);
Ejemplo3: Un último ejemplo, con las funciónes strcpy() y toupper() muy interesante dado el uso de
un vector como única componente de la condición a evaluar en un bucle for.
/* --------------------
Programa: cadena14.c
-------------------- */
# include <stdio.h>
# include <string.h>
# define DIEZ "\x1B[10;10f"
main()
{
char s1[80];
int i;
system("clear");
strcpy(s1,"esto es una prueba");
for(i=0;s1[i];i++) s1[i] = toupper(s1[i]);
printf(DIEZ);
printf(": <%s> :",s1);
printf("\n\n");
}
/* .. ..
La condicion de prueba del bucle for es el array que
indexa la variable de control. La razon de que este
programa funcione es que es verdad cualquier valor no 0.
Por tanto el bucle funciona hasta que encuentre el
terminador nulo, que es 0, el cual marca el fin de la
cadena.
.. .. */
CAPÍTULO 9
DIRECTIVAS PREPROCESADOR
9.1.- INTRODUCCION.
El preprocesador de "C" es un procesador de textos para manipular el texto de un fichero
fuente antes de la compilación.
Las directivas de preprocesador se usan normalmente para conseguir programas fuente fáciles
de modificar y ejecutarlos en diferentes entornos de ejecución. Estas directivas como parte de las
instrucciones de un fichero fuente ejecutarán diferentes acciones; por ejemplo el preprocesador puede
reemplazar palabras clave en el texto, insertar el contenido de otros fi-cheros en el fichero actual,
suprimir la compilación de una parte del fichero fuente, etc.
#define #ifdef
#elif #ifndef
#else #include
#endif #line
#if #undef
El signo numérico "#" tiene que ser el primer carácter no blanco de la línea que contiene la
sentencia directiva. Algunas sentencias directivas pueden ir seguidas por argumentos o valores y
pueden aparecer en cualquier parte de un fichero fuente.
Una vez definido el identificador, no puede redefinirse con un valor diferente sin borrar antes
su definicion previa. La directiva undef borra la definicion de un identificador. Una vez quitado el
identificador con #undef este se puede redefinir con un valor diferente.
Las macros pueden ser definidas como funciones de llamada, sin embargo pueden crear
problemas si no son definidas y usadas con cuidado; La definición de macros con argumentos puede
requerir el uso de paréntesis para preservar el grado de precedencia en una expresión.
Formato:
La directiva #define sustituye el texto dado por las ocurrencias del identificador en el fichero
fuente. Si aparece "lista_parametros" después del identificador la directiva #define reemplaza cada
ocurrencia del identificador (lista de argumentos) con una versión del texto modificado por la
sustitución de los argumentos actuales por parámetros formales.
Si el "texto" es mayor que una línea, este puede continuar en la siguiente incluyendo un
carácter backslash "\". El "texto" puede también estar vacío, su efecto es que quita todas las
ocurrencias del identificador del fichero fuente.
La "lista_parametros", cuando aparece, esta formada por uno o más nombres de parámetros
formales separados por comas; cada nombre en la lista tiene que ser único y la lista debe estar
encerrada entre paréntesis. No se permiten espacios entre el identificador y la apertura del paréntesis.
Los parámetros formales aparecen en el texto para marcar el lugar en donde los valores actuales serán
sustituidos.
Ejemplo:
1. #define ANCHO 80
#define LARGO (ANCHO+10)
El primer ejemplo define el identificador ANCHO como una constante entera de valor 80, y
define el identificador LARGO como otra de valor ANCHO+10. Cada ocurrencia de LARGO se
reemplaza con "ANCHO+10" que a su vez es reemplazado por "(80+10)". Los paréntesis son
importantes ya que controlan la interpretación en una sentencia, ejemplo:
var=LARGO*20;
var=(80+10)*20;
var=80+10*20;
El segundo ejemplo muestra un caso de una definicion extendida a mas de una línea usando el
carácter "\".
En el tercero se definen los identificadores REG1, REG2, REG3, los dos primeros se definen
con la palabra registro. La definicion de REG3 esta vacia por ello cada ocurrencia de REG3 se borra
del fichero fuente.
El cuarto ejemplo define una macro de nombre MAX. Cada ocurrencia del identificador MAX
se reemplaza por la expresión "((x)>(y))?(x):(y)" donde los valores actuales reemplazan los
parámetros "x" e "y". Por ejemplo la ocurrencia
MAX(1,2)
se reemplaza por
((1)>(2))?(1):(2)
y la ocurrencia
MAX(I,S[I])
por
((i)>(s[i]))?(i):(s[i])
En ciertos casos las macros pueden producir resultados inesperados, como en la siguiente
ocurrencia
MAX(i,s[i++])
((i)>(s[i++]))?(i):(s[i])
El tercer ejemplo define la macro MULT. Una vez definida esta una ocurrencia como
donde los paréntesis alrededor de los paréntesis tiene su importancia ya que controlan la interpretación
cuando expresiones complejas constituyen los argumentos de una macro. Por ejemplo:
MULT(3+4,5+6)
se reemplaza por
/* ------------------
Programa: direc1.c
------------------ */
# include <stdio.h>
# define TAMA_MAX 16
# define BORRA_P "\x1B[2J"
unsigned int potencias_de_dos[TAMA_MAX];
main()
{
int i;
printf(BORRA_P);
potencias_de_dos[0]= 1;
for(i=1;i<TAMA_MAX;i++)
potencias_de_dos[i] = potencias_de_dos[i-1] * 2;
for(i=10;i<TAMA_MAX;i++)
printf(" <%u> ",potencias_de_dos[i]);
printf("\n\n");
}
Ejemplo2: Definición de una macro, MIN(a,b), que calcula el mínimo de dos números.
/* ------------------
Programa: direc2.c
------------------ */
# include <stdio.h>
# define BORRA_P "\x1B[2J"
# define FILA10 "\x1B[10;10f"
# define FILA12 "\x1B[12;10f"
# define FILA14 "\x1B[14;10f"
# define FILA16 "\x1B[16;10f"
# define MIN(a,b) ((a)<(b)) ? (a) : (b)
main()
{
int x,y;
printf(BORRA_P);
x=10;
y=20;
printf(FILA10);
printf(" ... El mínimo de: <%2d> y <%2d> es <%2d> ", x,y,MIN(x,y));
printf(FILA12);
printf(" ... El mínimo de: <%2d> y <%2d> es <%2d> ", 2,5,MIN(2,5));
printf(FILA14);
printf(" ... El mínimo de: <%2d> y <%2d> es <%2d> ", 2*x,2*y,MIN(2*x,2*y));
printf(FILA16);
printf(" ... El mínimo de: <%2d> y <%2d> es <%2d> ", x+y,y-x,MIN(x+y,y-x));
printf("\n\n");
}
La directiva #undef va normalmente emparejada con la directiva #define para crear una región
en un programa fuente en el cual un identificador tenga un significado especial. Por ejemplo, una
función especifica del programa fuente puede usar unas constates para definir un entorno especifico de
valores que no afecten al resto del programa. La directiva #undef también se utiliza conjuntamente con
la directiva #if para controlar partes de la compilación de un programa fuente.
Ejemplo:
#define ANCHO 80
#define SUMAR(X,Y) (X)+(Y)
.
.
#undef ANCHO
#undef SUMAR
En este ejemplo la directiva #undef quita las definiciones de una constante y una macro. Véase
como únicamente se indica el identificador. La directiva #undef se puede apli-car también a un
identificador que no se ha definido previamente para asegurar que el iden-tificador está indefinido.
Ejemplo3: Uso de la sentencia directiva "undef" para quitar el valor actual de un identificador.
/* ------------------
Programa: direc3.c
------------------ */
# include <stdio.h>
# define BORRA_P "\x1B[2J"
# define LONGITUD 100
# define ANCHO 100
int array[LONGITUD][ANCHO];
# undef LONGITUD
# undef ANCHO
# define LONGITUD 3
# define ANCHO 3
main()
{
int i,j;
printf(BORRA_P);
for (i=0;i<3;i++)
for (j=0;j<3;j++)
array[i][j] = i+j;
/* IMPRESION */
/* --------- */
for (i=0;i<3;i++)
for (j=0;j<3;j++)
printf(" <%2d> ",array[i][j]);
printf("\n\n");
}
#include camino
#include <camino>
El procesador usa el concepto de directorio(s) "standard" para buscar los ficheros incluidos. La
localización de estos directorios depende de la implementación y del sistema operativo. El procesador
para la búsqueda tan pronto como encuentre un fichero con el nombre dado.
Si se indica el camino entre comillas ("") o entre ángulos (<>), el preprocesador busca solo en
esos caminos y ignora los directorios standard. Si no se da la especificación completa del "camino" y
la especificación esta encerrada entre comillas , el preprocesador busca el fichero en el directorio de
trabajo actual. Luego busca en los directorios especificados en la línea de comandos del compilador y
finalmente busca en los directorios estándares.
Ejemplo:
1. #include <stdio.h>
2. #include "defs.h"
El primer ejemplo añade el contenido del fichero stdio.h a programa fuente actual. Los
símbolos "<>" provocan que el preprocesador busque en los directorios estándares el fichero stdio.h,
después de buscar en los directorios especificados en la línea de comando.
El segundo ejemplo añade el contenido del fichero defs.h al programa fuente. La comillas
dobles provocan que se busque en primer lugar en el directorio que contiene el programa fuente actual.
Formato:
#if rest_constante_expresión
[texto]
[#elif rest_constante_expresión
texto]
[#elif rest_constante_expresión
texto]
.
.
[#else
texto]
#endif
La directiva #if, junto con las directivas #elif,#else, y #endif, controlan la compilación de
partes de un programa fuente. Cada directiva #if tiene que finalizar con una directiva #endif.
Cero o más directivas #elif pueden aparecer entre las directivas #if y #endif, pero solo se
permite una directiva #else. La directiva #else, si esta presente, tiene que figurar antes que #endif.
Las directivas #if, #elif, #else y #endif pueden anidarse dentro del texto de otra directiva #if.
Cuando se anidan, cada directiva #else, #elif y #endif pertenece al directiva #if que le precede.
Ejemplos:
1. #if definida(CREDITO)
credito();
#elif definida(DEBITO)
debito();
#else
imprimerror();
#endif
2. #if NIVELD>5
#define SIGNO 1
#if PILAUSO==1
#define PILA 200
#else
#define PILA 100
#endif
#else
#define SIGNO 0
#if PILAUSO==1
#define PILA 100
#else
#define PILA 50
#endif
#endif
3. #if NIVELD==0
#define PILA 0
#if NIVELD==1
#define PILA 100
#elif NIVELD > 5
display(debugptr);
#else
#define PILA 200
#endif
En el primer ejemplo, las directivas #if y #endif controlan la compilación de una de las tres
funciones llamadas. La función crédito() se compilará solo si el identificador CREDITO esta definido.
Si el identificador DEBITO esta definido la función debito se compilara. Si ningún identificador esta
definido se compilará la función imprimerror().
Los siguientes dos ejemplos suponen definidos la constante NIVELD. El segundo ejemplo
muestra dos conjuntos de sentencias directivas #if, #else y #endif anidadas. El primer conjunto de
directivas se ejecuta solo si "NIVELD >5" es cierto, en caso contrario se ejecuta el segundo conjunto.
En el tercer ejemplo, las directivas #elif y #else son usadas para hacer cambios según se el
valor de NIVELD. La constante PILA tomará un valor 0, 100 o 200 dependiendo del valor de
NIVELD, si no esta definido, "display(debugptr);" se compilará y Pila quedará indefinido.
/* ------------------
Programa: direc4.c
------------------ */
# include <stdio.h>
# define BORRA_P "\x1B[2J"
# define FILA10 "\x1B[10;10f"
# define FILA20 "\x1B[20;10f"
# define MAX 100
main()
{
int i=0;
printf(BORRA_P);
# if MAX > 99
printf(FILA10);
printf("COMPILANDO PARA UN ARRAY MAYOR \n");
# else
printf(FILA20);
printf("COMPILANDO PARA UN ARRAY PEQUEÑO \n");
# endif
}
/* ------------------
Programa: direc5.c
------------------ */
# include <stdio.h>
# if PAIS_ACTIVO == AMERICA
char moneda[]="dolar americano";
# elif PAIS_ACTIVO == INGLATERRA
char moneda[]="libra inglesa";
# else
char moneda[] = "franco frances";
# endif
main()
{
printf(BORRA_P);
printf(FILA10);
printf("La moneda seleccionada es <%s> \n",moneda);
}
Formato:
#ifdef identificador
#idndef identificador
La directivas #ifdef y #ifndef se usan conjuntamente con las directivas #if y #define. Cuando el
procesador encuentra una directiva #ifdef chequea el identificador para ver si esta definido. Si es así,
#ifdef vale cierto (no 0), en otro caso vale falso (0).
Ejemplo: Comprobando en si ciertas constantes están definidas o no con las directivas "ifdef" y
"ifndef".
/* ------------------
Programa: direc6.c
------------------ */
# include <stdio.h>
# define BORRA_P "\x1B[2J"
# define FILA10 "\x1B[10;10f"
# define FILA15 "\x1B[15;10f"
# define JON 100
main()
{
printf(BORRA_P);
# ifdef JON
printf(FILA10);
printf("- HOLA JON ! Te debo <%d> Pts \n",JON);
# else
printf(FILA10);
printf("-HOLA A CUALQUIERA! no debo nada a nadie \n");
# endif
# ifndef RAQUEL
printf(FILA15);
Cambiar el número de línea y el nombre de fichero hace que el compilador ignore los valores
por defecto y proceda con los nuevos valores. El valor constante de la directiva #line puede ser
cualquier constante entera. El "nombre_fichero" puede estar formado por cualquier combinación de
caracteres y debe estar encerrado entre comillas.
Ejemplos:
CAPÍTULO 10
EJEMPLO
/* -----------------
Programa: Juego.c
JUEGO SUBMARINO..
-----------------
*/
/*
.. ..
El acorazado y el submarino.
( Un simple juego de ordenador )
La computadora controla el submarino
El usuario controla el acorazado.
Tanto el submarino como el acorazado navegan por el océano, ambos se
encuentran en continuo movimiento. Cada uno trata de alcanzar al otro
con uno de sus misiles.
.. ..
*/
/*
.. ..
El océano es un cuadro 3*3. (matriz[3][3])
Por ello los disparos en coordenadas X,Y
deben de figurar en el rango X: 0 - 2
Y: 0 - 2
( Siendo el angulo superior izquierdo de
coordenadas -> 0,0
.. ..
*/
# include <stdio.h>
# include <time.h>
# define BORRA_P "\x1B[2J"
# define FILA15 "\x1B[15;15f"
# define FILA17 "\x1B[17;15f"
# define FILA25 "\x1B[6;27f"
# define ESPACIO ' '
# define SUBMARINO 'S'
# define ACORAZADO 'A'
char matriz[3][3] =
{
ESPACIO,ESPACIO,ESPACIO,
ESPACIO,ESPACIO,ESPACIO,
ESPACIO,ESPACIO,ESPACIO
};
main()
{
printf(BORRA_P);
compX=compY=jugadorX=jugadorY=0;
visualizar_océano();
for(;;)
{
if (submarino())
{
visualizar_océano();
printf(FILA15);
printf("(((( El submarino ha hundido al acorazado.))))");
printf(FILA17);
printf("GANA: ! SUBMARINO ! \n");
break;
}
if (jugador())
{
visualizar_océano();
printf(FILA15);
printf("(((( El acorazado ha hundido al submarino. ))))");
printf(FILA17);
printf("GANA: ! ACORAZADO ! \n");
break;
}
visualizar_océano();
}
}
/* Función submarino */
/* ----------------- */
/*
Genera el siguiente movimiento utilizando el generador de numeros aleatorios.
*/
submarino()
{
matriz[compX][compY] = ESPACIO;
compX = rand() % 3;
compY = rand() % 3;
/*--------------------------------*/
if(matriz[compX][compY] == ACORAZADO)/* Si el movimiento generado -*/
return 1; /* GANA SUBMARINO */ /* tiene las mismas coordenadas -*/
else /* que las de la posicion actual -*/
{ /* del acorazado. -*/
matriz[compX][compY] = SUBMARINO; /* GANA SUBMARNO. -*/
return 0; /* PIERDE ACORAZADO */ /*--------------------------------*/
}
}
/* función jugador */
/* --------------- */
jugador()
{
matriz[jugadorX][jugadorY] = ESPACIO;
do
{
printf(FILA25);
printf("Introduzca coordenadas (X,Y): ");
scanf("%d,%d",&jugadorX,&jugadorY);
}
/* Función visualizar_océano */
/* ------------------------- */
visualizar_océano()
{
int x, y;
printf(BORRA_P);
printf("\n");
printf(" <0> <1> <2> \n");
printf(" ------------------- \n");
printf("<0>. %c . %c . %c . \n",matriz[0][0],matriz[0][1],matriz[0][2]);
printf(" ------------------- \n");
printf("<1>. %c . %c . %c . \n",matriz[1][0],matriz[1][1],matriz[1][2]);
printf(" ------------------- \n");
printf("<2>. %c . %c . %c . \n",matriz[2][0],matriz[2][1],matriz[2][2]);
printf(" ------------------- \n");
}
CAPÍTULO 11
CORRIENTES Y ARCHIVOS
Por ello, la entrada estándar de datos (por teclado) se asocia con un archivo de nombre “stdin”
(Standard Input o entrada de datos estándar), y la salida por pantalla con “stdout” (Standard Ouput o
salida de datos estándar).
- Corrientes de texto.
- Corrientes binarias.
Habitualmente, este tipo de agrupación de líneas o registros (fichero o archivo) termina con el
carácter ASCII decimal 26 (EOF, de End Of File o fin de fichero).
El actual estándar ANSI propuesto no establece este carácter de separación como obligatorio,
dejándolo como opcional; no obstante, es el utilizado por la mayor parte de las implementaciones de
“C”.
Al tratar una corriente de texto “C” realiza ciertas transformaciones de los caracteres
especiales leídos/escritos. Así, por ejemplo, un carácter LF puede transformarse en un pro (CR y LF)
según las conveniencias del Sistema. Por tanto hay ciertas diferencias o viceversa, cuando se trata el
archivo como archivo de texto.
“C” no escribe, salvo indicación expresa, el carácter EOF al cerrar un archivo, el cual marca el
final de mismo.
Un archivo “C” puede tener diferentes tipos de organización (secuencial, directa e indexada),
formas que están relacionadas con la estructura (longitud constante o variable) de la agrupación den
líneas o registros de los bytes del mismo.
Las entradas/salidas UNIX a nivel del Sistema utilizan, en forma similar a otros lenguajes de
alto nivel, descriptores de archivos o handles.
FILE *NomPunteroFichero;
NomPunteroFichero = fopen(NomFichero,ModoApertura);
donde:
ModoApertura: se refiere al modo de apertura del archivo y puede ser alguno de los
siguientes:
“r” Abrir archivo de texto sólo para lectura. El archivo debe existir.
“w” Abrir archivo de texto sólo para escritura. Si el archivo existe, se destruye.
“a” Abrir archivo de texto para añadir datos. Si el archivo no existe, se crea.
“rb” Abrir archivo binario para lectura.
“wb” Abrir archivo binario para escritura.
“ab” Abrir archivo binario para añadir datos.
“r+” Abrir archivo de texto para escritura/lectura. El archivo debe existir.
“w+” Crear archivo de texto para lectura/escritura. Si el archivo existe, se destruye.
“a+” Abrir archivo de texto para lectura/escritura. Si el archivo no existe se cra.
“rb+” Abrir archivo binario para lectura/escritura.
“wb+” Crear archivo binario para lectura/escritura.
“ab+” Abrir archivo binario para lectura/escritura.
que realiza la apertura del archivo si la dirección de almacenamiento del mismo existe, y en caso
contrario (puntero NULL), imprime un mensaje de error y se produce la terminación del programa en
ejecución.
La función:
fclose(NomPunteroFichero)
En la práctica normal el signo EOF es un carácter especial, no confundible con cualquier otro
símbolo presente en el archvio, concretamente el carácter ASCII 26 (en decimal). En “C”, EOF es una
constante manifiesta, definida en el archivo de cabecera <stdio.h> que, en archivos escritos en modo
texto utilizando “C” o Editores de Texto ASCII, toma valor entero –1 cuando el Sistema detecta el
último carácter del archivo. En el caso de archivos escritos con Editores de Texto, este último carácter
es el ASCII 26.
Para solucionar este problema, “C” dispone de la función feof (FILE *PunteroFichero) que
detecta el final del archivo por la situación del puntero del Sistema en el mismo o, más claramente, por
el byte en que éste está situado respecto al principio del archivo (el nombre que utilizaremos en
nuestro programa) y el dispositivo físico en que éste se almacena, en el proceso de apertura del
archivo, “C” toma y retiene, de forma automática (entre otros datos), la posición inicial del archivo
dentro del disco y el número de bytes que tiene. Proceso transparente para el usuario.
Hay que resaltar que la forma de acceso a un registro (secuencial, directa o por índice) no está
limitada por la organización con la que se creó el archivo, sino por la estructura de los registros del
mismo. De echo en “C” no es necesario definir la organización a la hora de acceder a los datos de un
archivo.
- Función fpuct.
Formato:
#include <stdio.h>
int fputc(VarCarácter/Carácter,Corriente)
int VarCarácter;
FILE *Corriente;
- Función fgetc.
Formato:
#include <stdio.h>
int fgetc(Corriente)
FILE *Corriente;
Si se alcanza el final de un archivo de texto, fgetc( ) devuelve un carácter EOF que permite
controlar el final del archivo en corrientes de texto. No obstante, al ser EOF un carácter más en
corrientes binarias, para detectar el final del archivo en éstas es necesario utilizar la función feof( )
para controlar el final de estos archivos.
Si por otra parte, se produce un error de lectura, fgetc( ) devuelve también un carácter EOF, y
para localizar el error es necesario utilizar la función ferror( ).
Como norma, al fin de evitar quebraderos de cabeza, utilice feof( ) para localizar el final de
cualquier fichero.
/* --------------------
Programa: fich1_se.c
--------------------- */
# include <stdio.h>
main( )
{
FILE *Corriente;
Char Carácter=’a’;
If ((Corriente=fopen(“carácter”,”W”))==NULL)
{
system(“clear”);
printf(“Imposible abrir el fichero pepe”);
exit (0);
}
fputc(Carácter,Corriente);
fputc(‘b’,Corriente);
fclose(Corriente);
/* .. ..
Lectura de los dos caracteres del fichero.
.. .. */
if ((Corriente=fopen(“carácter”,”r”))==NULL)
{
system(“clear”);
printf(“Imposible abrir fichero pepe”);
exit(0);
}
system(“clear”);
printf(“\n\n\n”);
Carácter=fgetc(Corriente);
printf(“Primer carácter leido -> < %c > \n\n”, Carácter);
Carácter=fgetc(Corriente);
printf(“Segundo carácter leido -> < %c > \n\n”,
Carácter);
Carácter=fgetc(Corriente);
printf(“Tercer carácter leido -> < %d > \n\n”, Carácter);
fclose(Corriente);
}
En este ejemplo se abre un archivo de nombre “carácter” apuntado por Corriente y se escriben
los caracteres indicados, cerrándolo a continuación. La posterior apertura, lectura e impresión del
archivo dan como resultado por pantalla:
Formatos:
#include <stdio.h>
int putc(VarCarácter/Carácter,Corriente)
int VariableCarácter;
FILE *Corriente;
#include <stdio.h>
int getc(Corriente);
FILE *Corriente;
En el siguiente ejemplo se lee un archivo en modo binario, utilizando para el control del fin
del archivo el valor que tomará la macro EOF al detectarse el final del mismo.
Este programa funcionara con corrientes de texto, pero no con corrientes binarias. Si abre un
archivo ejecutable en el modo binario para lectura
/* --------------------
Programa: fich2_se.c
-------------------- */
# include <stdio.h>
main( )
{
FILE *fp;
int ch;
if ((fp=fopen(“carácter”,”rb”))==NULL)
{
printf(“Imposible abrir archivo”)
exit(-1);
}
system(“clear”);
printf(“\n”);
do
{
ch=getc(fp);
printf(“Carácter : < %d >\n\n”,ch);
}
while (ch != EOF);
printf(“\n\n”);
if (ch == EOF)
printf(“EOF vale < %d > \n\n”, ch);
fclose(fp);
}
Veamos un segundo no programa que no tiene desperdicio. Realmente el programa utiliza dos
corrientes pero sólo declara una. La corriente de entrada es un archivo existente, cuyo nombre de
entrada es el nombre del archivo al que se quiere acceder de forma secuencial carácter a carácter.
Debería existir una segunda definición de corriente para el archivo de salida (la pantalla), pero
no es necesario. Precisamente (stdout) es la que aquí utilizaremos como segundo archivo par salida por
pantalla.
/* --------------------
Programa: fich3_se.c
-------------------- */
#include <stdio.h>
main( )
{
FILE *Corriente;
char nombre[30];
char carácter;
system(“clear”);
printf(“Introduzca el nombre del archivo de texto : “);
scanf(“%s”,nombre);
system(“clear”);
if ((Corriente = fopne(nombre,”r”)) ==NULL)
{
printf(“\n\nImposible abrir el archivo de texto :
%s”,nombre);
printf(“\n\n”);
exit(0);
}
/* .. ..
Impresión del fichero.
.. .. */
Ejemplo: En este tercer ejemplo nos creamos una orden de copia propia.
/* --------------------
Programa: fich4_se.c
-------------------- */
/*
.. ..
Copia un fichero de cualquier tipo.
.. .. */
# include <stdio.h>
main(argc,argv)
int argc;
char *argv [ ];
{
FILE *in, *out;
char ch,aux;
if (argc != 3)
{
system(“clear”);
printf(“\n\n”);
printf(“Olvido introducir los nombres de los ficheros....\n\n”);
printf(“<-> Formato: ./a.out fich_origen fich_destino <-
>\n\n\n”);
exit(1);
}
if ((in=fopen(argv[1],”rb”)) = = NULL)
{
system(“clear”);
printf(“\n\n No puedo abrir el fichero fuente. \n\n\n”);
}
if ((out=fopen(argv[2],”w”)) = = NULL)
{
system(“clear”);
printf(“\n\n No puedo abrir el fichero destino. \n@);
exit(1);
}
system(“clear”);
printf(“\n\n\n”);
print( --> Proceso de copia <-- \n\n”);
print(“COPIANDO : <%s> <> CREANDO : <%s> \n”,argv[1],argv[2]);
printf(“\n\n Pulse <Return> para comenzar copoia. “);
aux=getchar ( );
while(!feof(in))
putc(getc(in),out);
fclose(in);
fclose(out);
}
$ cat mariscos
Langosta Langostino Gamba
Almeja Vieira Mejillón
Etc.
$. /a.out marisco
copia
Proceso de copia
$ cat copia
Langosta Langostino Gamba
Almeja Vieira Mejillón
Etc.
Un caso erróneo:
$ ./a.out
Las funciones putw( ) y getw( ) no están definidas por el estandar ANSI propuesto y pueden
causar problems de traansportabilidad de los programs entre unos Sistemas Operativos y otros.
Formatos:
#include <stdio.h>
int putw(Entero, Corriente)
int Entero;
FILE *Corriente;
#include <stdio.h>
int getw(Corriente)
FILE *Corriente;
- Función fputs( )
Formato:
#include <stdio.h>
int fputs (VarCadena/Cadena,Corriente)
char *VarCadena;
FILE *Corriente;
- Función fgets( )
Los caracteres se leen hasta que se encuentre un carácter LF o EOF o se llegue al límite
especificado.
Una vez leídos los caracteres, incluido el LF, “C” añade al final de VarCadena el carácter nulo
(‘\0’) como parte integrante de la misma. Si Num = 1, la cadena leída es la cadena vacía (“ “).
Formato:
#include <stdio.h>
char *fgets(VarCadena,Num,Corriente)
char *VarCadena;
int Num;
FILE *Corriente;
Ejemplo:
En el siguiente programa, se abre un archivo existente para lectura en binario. Con la función fgets( ),
se lee línea a línea, y se imprime línea a línea con la función fputs( ) por el dispositivo (archivo)
estándar de salida stdout, en este caso el dispositivo por defecto, la pantalla. El programa finaliza
cuando se llega al final del archivo, lo que se comprueba con la función feof (Corriente).
/* --------------------
Programa: fich5_se.c
-------------------- */
# include <stdio.h>
main( )
{
char archivo[35];
char cadena[75];
FILE *pf;
system(“clear”);
printf(“Introduzca el nombre del fichero a leer : “);
scanf(“%s”,archivo);
if ((pf = fopen(archivo,”rb”))==NULL)
{
printf(“\n\n Imposible abrir archivo: < %s >
\n\n”,archivo);
exit(0);
}
system(“clear”);
printf(“\n\n Fichero -> %s “,archivo);
printf(“\n ------------------------- \n\n”);
while (!feof(pf))
{
fgets(cadena,25,pf);
fputs(caden,stdout);
}
fclose(pf);
printf(“\n\n”);
}
Ejemplo:
Este otro programa es una variación del anterior, en que tanto el nombre del archivo (archivo) como el
de la variable de caracteres (cadena) son punteros a la dirección en que ambos datos se almacenarán.
Observa la forma en que se leen los datos con la condición “if feof(pf)” para evitar un mensaje
de error al intentar efectuar una lectura fuera del archivo. Este error se deberá a que la lectura de los
datos se realiza desde la posición inicial en memoria de los mismos, en el número de caracteres
especificado, sin tener en cuenta los caracteres especiales de control.
/* --------------------
Programa: fich6_se.c
-------------------- */
#include <stdio.h>
main( )
{
char *archivo;
char *cadena;
FILE *pf;
system(“clear”);
printf(“Introduzca nombre del archivo a leer: “);
scanf(“%s”,archivo);
if ((pf = fopen(archivo,”rb”)) == NULL)
{
printf(“\n\n Imposible abrir el archivo:
%s\n\n”,archivo);
exit(0);
}
system(“clear”);
- Función fprintf( )
Formato:
#include <stdio.h>
int printf(Corriente,”Formatos”,Lista de Variables)
FILE *Corriente;
- Función fscant( )
La función fscanf( ) lee datos desde Corriente almacenándolos en Lista de Variables. Actúa
igual que la funcnión scanf( ), pero tomando los datos desde la corriente especificada.
Formato:
#include <stdio.h>
int fscanf(Corriente,”Formatos”,Lista de Variables)
FILE *Corriente;
Ejemplos:
/* --------------------
Programa: fich7_se.c
-------------------- */
# include <stdio.h>
main( )
{
char color[15];
int codigo;
long int precio;
int cantidad;
FILE *pf;
/* .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
._._._._._._._._._._._._._._._._._._._._. */
system(“clear”);
if ((pf = fopen(“rotulador”,”w”)) == NULL)
{
printf(“\n\n Imposible abrir archivo < rotulador > \n\n”);
exit(0);
}
printf(SIETE);
printf(“Código (fin = 0) :-> “);
scanf(“%d”,&codigo);
/* --------------------
Programa: fich8_se.c
-------------------- */
# include <stdio.h>
main( )
{
char color [15],a;
int codigo;
long int precio;
int cantidad;
FILE *pf;
/* .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
._._._._._._._._._._._._._._._._._._._._. */
system(“clear”);
while (!feof(pf))
{
fscanf(pf,”%d %s %1d %d”,&codigo, color, &precio, &cantidad);
if (feof(pf))
exit(0);
printf(FINAL);
printf(“ < Return > -> Proximo registro”);
printf(SIETE);
printf(“Codigo .... : %12d*”,codigo);
printf(NUEVE);
printf(“Color .... : %12s *”,color);
printf(ONCE);
printf(“Precio .... : %131d *”,precio);
printf(TRECE);
printf(“Cantidad .. :%12d *”,cantidad);
a=getchar( );
System(“clear”);
}
fclose(pf);
}
- Función fwrite( ).
La función fwrite( ) escribe Num elementos de Long número de bytes (Ver formato) desde la
zona de memoria apuntada por Buffer a Corriente. La función fwrite( )devuelve el número de
caracteres leídos. Si Corriente se abre en forma texto, cada carácter CR de Buffer se sustituye por el
par de caracteres CR LF. El reemplazo de caracteres no tiene efecto sobre el valor devuelto (número
de caracteres escritos). Esta transformación de caracteres no tiene lugar cuando el archivo se abre en
binario.
Formato:
#include <stdio.h>
int fwrite(Buffer, Long, Num, Corriente)
const void *Buffer;
int Long, Num;
FILE *Corriente;
-Función fread( )
Buffer puede ser, y lo es a menudo, una variable de cualquier tipo cuya dirección se indica con
el operador de dirección &. Otras veces, puede asignarse Buffer dinámicamente.
Formato:
#include <stdio.h>
int fread(Buffer, Cadena, Long, Num, Corriente)
const void *Buffer;
int Long, Num;
FILE *Corriente;
Una de las mejores aplicaciones de fwrite( ) y fread( ) consiste en la escritura y lectura de los
tradicionalmente llamados registros, conjunto de datos escritos/leídos en su archivo, que en “C”
reciben o se tratan como datos de tipo struct (estructura). Su capacidad para tratar cualquier tipo de
datos, ya sean cadenas de caracteres, variables simples o arrays numéricos, puede observarse en los
siguientes ejemplos.
Ejemplos 1,2: En el primer caso escribimos un valor flota en un fichero, y en segundo efectuamos su
lectura.
/* --------------------
Programa: fich9_se.c
-------------------- */
/*
.. ..
Escribe un numero en punto flotante a un fichero
en disco.
.. .. */
# include <stdio.h>
main( )
{
FILE *fp;
float f=12.23;
if ((fp=fopen(“test”,”wp”)) == NULL)
{
printf(“ No puede abrir el fichero <test>. \n”);
}
fwrite(&f,sizeof(float),1,fp);
fclose(fp);
}
/* --------------------
Programa : fich10_se.c
-------------------- */
/*
.. ..
Escribe un numero en punto flotante a un fichero
en disco.
.. .. */
# include <stdio.h>
# define DIEZ “\x1B[10;20f”
main( )
{
FILE *fp;
float f;
system(“clear”);
if ((fp=fopen(“test”,”rb”)) == NULL)
{
printf(“No puede abrir el fichero <test>.\n”);
printf(“<-> <->\n”);
exit(1);
}
fread(&f,sizeof(float),1,fp);
printf(DIEZ);
printf(“Valor del unico float del fichero <test> ->%.2f<-
\n”,f);
fclose(fp);
}
/* --------------------
Programa: fich11_se.c
-------------------- */
/* .. ..
Una de las aplicaciones más útiles de fread( ) y fwrite( )
es la lectura y escritura de vectores.
.. .. */
# include <stdio.h>
main( )
{
FILE *fp;
float muestra[10];
int 1;
if ((fp=open(“muestra”,”wb” == NULL)
{
printf(“No puedo abrir el fichero <muestra>.\n”);
printf(“<-> <->\n”);
exit(1);
}
for(i=0;1<10;1++)
muestra[[i] = (float) (i*.5);
fclose(fp);
}
/* --------------------
Programa: fich12_se.c
-------------------- */
/* .. ..
Una de las aplicaciones más utiles de fread( ) y fwrite( )
es la lectura y escritura de vectores.
.. .. */
# include <stdio.h>
main( )
{
FILE *fp;
float ejemplo[10];
int i;
if ((fp=fopen(“muestra”,”rb”)) == NULL)
{
system(“clear”);
printf(“\n\n”);
printf(“ Este es el contenido del vector leído.\n”);
printf(“ ----------------------------------------- \n”);
printf(“\n”);
for(i=0;i<10;1++)
printf(“ <%.2f> 2,ejemplo[i]);
fclose(fp);
}
Hasta ahora hemos escrito/leido los datos como caracteres o cadenas, vemos ahora la
escritura/lectura de datos en la forma tradicional, es decir, utilizando los ya tradicionales registros
compuestos de campos. Hay que pensar que ya disponemos de todas las librerías necesarias para crear
los registros que ahora vamos a utilizar con el tipo de dato estructura (struct).
Ejemplos:
La creación de archivos como registros definidos por el usuario no difiere esencialmente del sistema
seguido en los diferentes lenguajes de alto nivel. En este caso, la función gets( ) lee los caracteres
introducidos por el teclado hasta que se pulse la tecla <RETURN>. Como gets( ) lee caracteres, no
valores numéricos, y lo que escribimos en el archivo es una estructura mixta, es necesario transformar
estos valore previamente a su grabación en el registro, lo que se hace utilizando las funciones atoi( ),
atol( ) y atof( ).
/* --------------------
Programa: fich13_se.c
-------------------- */
#include <tdio.h>
#define NCOD 4
#define NAPE 35
#define NOV 25
#define NCOS 10
struct NOMINA
{
char CODIGO[NCOD];
char APE[NAME];
char NOMBRE[NOV];
char CODSS[NCOS];
int IRPF;
int long SUELDO;
};
main( )
{
FILE *PuntFi;
char CIRPF[10];
char CSUELDO[10];
struct NOMINA PAGOS;
system (“clear”);
print(“Nombre : ”);
gets(PAGOS.NOMBRE);
printf(DOCE);
printf(“Código de la S.Social : “);
gets(PAGOS.CODSS);
printf(CATORCE);
printf(“I.R.P.F. : “);
gets(CIRPF);
printf(DIECISÉIS);
printf(“Sueldo : “);
gets(CSUELDO);
PAGOS.IRPF = atoi(IRPF);
PAGOS.SUELDO = ato1(CSUELDO);
if (fwrite(&PAGOS,sizeof(struct NOMINA),1,PuntFi) != 1)
{
printf(“\nError de escritura \n”);
}
system(“clear”);
printf(CINCO),
printf(“Código del trabajador.”);
printf(SEIS);
printf(“Pulse \”ZZ\” para terminar: “);
gets (PAGOS.CODIGO);
}
system(“clear”);
}
/* --------------------
Programa: fich14_se.c
-------------------- */
#include <stdio.h>
#define NCOD 4
#define NAPE 35
#define NOV 25
#define NCOS 10
struct NOMINA
{
char CODIGO[NCOD];
char APE[NAME];
char NOMBRE[NOV];
char CODSS[NCOS];
int IRPF;
int long SUELDO;
};
main( )
{
FILE *PuntFi;
char CIRPF[10];
char CSUELDO[10];
char aux;
struct NOMINA PAGOS;
system(“clear”);
while (!feof(PuntFi))
{
printf(SEIS);
printf(“Código del trabajador : %s “, PAGOS.CODIGO);
printf(OCHO);
printf(Apellidos : %s “, PAGOS.APE);
printf(DIEZ);
printf(“Nombre : %s “, PAGOS NOMBRE);
printf(DOCE);
printf(“Código de la S.Social : %s “,PAGOS CODSS);
printf(CATORCE);
printf(“I.R.P.F. : %d “,PAGOS.IRPF);
printf(DIECISÉIS);
printf(“Sueldo : %1d ”,PAGOS.SUELDO);
if(fread(&PAGOS, sizeof(struct NOMINA),1,PuntFi) != 1)
{
printf(“\n\n\n”);
printf(“ Ultimo registro. (Fin Achivo.)”);
}
printf(VEINTITRES);
printf(“Pulse <Return> para continuar.”);
aux=getchar( );
system(“clear”);
}
}
Hasta ahora, hemos escrito tiras de caracteres. Vemos un ejemplo donde se escriben valors
numéricos.
/* --------------------
Programa: fich15_se.c
-------------------- */
#include <stdio.h>
FILE *corri;
int numeros[100];
int numlee, numesc, i;
int n = 50;
main( )
{
system(“clear”);
for (i=0; i<= 99; i++)
numeros[i]=n++;
if ((corri = fopen(“enteros”,”wb”)) != NULL)
{
numesc = fwrite((char *)numeros,sizeof(int),100,corri);
printf(“\n Numero de items escritos : <%d>”,numesc);
}
else
{
printf(“No puede abrirse el archivo \”enteros\””);
exit(-1);
}
fclose(corri);
/* ......................................................................................................
----------------------------------------------------------------------------- */
corri = fopen(“enteros”,”rb”);
numlee = fread((char *)numeros, sizeof(int),100,corri);
print(“\n Numero de items leídos : <%d>”,numlee);
print(“\n El primer item leído es : <%d>”, numeros[0]);
print(“\n\n”);
fclose(corri);
}
Hemos escrito y leido, posteriormente, 100 datos de tipo int en sendas operaciones de
escritura/lectura. Para escribir/leer hemos moldeado el tipo de datos con (char *) numeros, y para
asegurar la portabilidad del programa hemos dado la longitud de cada dato con sizeof(int).
Al igual que otros lenguajes, “C” permite también el acceso directo a los datos almacenados
en un archivo. Sin embargo, a diferencia de la mayoría de ellos, “C” admite desplazamientos byte a
byte dentro de un archivo.
- Función fseek( )
Formato:
#include <stdio.h>
int feb(Corriente, Desplazamiento, Origen)
FILE *Corriente;
long Desplazamiento;
int Origen;
donde:
El Origen desde el que se efectúa el desplazamiento puede ser alguno de los siguientes:
La función fseek( ) elimina, si se sitúa sobre ellos, los indicadores de final de archivo, de esto
se deduce que es responsabilidad del programador no situarse fuera del archivo.
Hay que tener en cuenta que un fseek( ) seguido de un operación de lectura hace que se lea a
partir del carácter siguiente a la posición en que se colocó fseek( ) el puntero del Sistema.
- Función ftell( )
Formato:
#include <stdio.h>
long ftell(Corriente)
FILE *Corriente;
La función ftell(Corriente) devuelve la posición actual del puntero del Sistema tomando como
origen el principio del archivo. En el siguiente ejemplo, se crea un archivo y se graba una cadena
alfanumérica. La gunción fseek( ) con origen en el principio del archivo “0” sitúa al puntero del
archivo en el carácter 7 desde el principio del archivo. La función fgets( ) lee ocho caracteres,
comenzando por el octavo, y los almacena en “cadena” añadiendo el carácter nulo al final de los
caracteres leídos. Ver la salida de este programa al final del código.
/* --------------------
Programa : fich_di.c
-------------------- */
#include <stdio.h>
main( )
{
char cadena[9];
FILE *fichero;
int numero;
long desplaza;
fichero=fopen(“numeros”,”w+”);
fprintf(fichero,”%s”,”123456789ABCDEFGHIJK”);
system(“clear”);
desplaza = 7;
fseek(fichero, desplaza, 0);
printf(“ Posición : <%2d> \n”,ftell(fichero));
fgets(cadena,8,fichero);
printf(“ Cadena : <%9s> \n”, cadena);
printf(“\n\n”);
printf(“ -> Al leer el puntero pasa a la siguiente posición.\n”);
fclose(fichero);
}
Da como salida:
CAPÍTULO 12
ENTRADAS Y SALIDAS A NIVEL DEL SISTEMA
Un descriptor de archivo no es más que una serie ordenada de bytes que se crea al abrir el
mismo y que contiene los datos necesarios para el manejo del archivo por el Sistema Operativo.
De forma similar, un puntero tipo FILE es un puntero a una estructura especial, definida en
<stdio.h>, que contiene la información que necesita el Sistema Operativo para utilizar el archivo.
Aunque existen funciones para relacionar handles y punteros tipo FILE es aconsejable que se
utilicen handles si se está trabajando a nivel de Sistema, y punteros FILE si se trabaja en C estándar
ANSI.
En C a nivel del Sistema se consigue una mayor velocidad de ejecución, puesto que sus
funciones están más próximas al Sistema Operativo.
- Función open( )
Esta función, procedente del UNIX, no se incluye en el estándar ANSI propuesto. Open abre
un descriptor de fichero para el nombre de fichero indicado y configura los flags de estado del fichero
en función de los valores de Modo (Ver formato).
-Formato:
#include <stdio.h>
#include <fcntl.h>
int open(NomFichero, Modo, [Permisos])
char *NomFichero;
int Modo;
int Permisos;
Modo: Expresa la forma de apertura del archivo. Esta opción se construye en función de las siguientes
posibilidades.
O_RDONLY: Abre el archivo para sólo lectura. Este Modo no puede utilizarse conjuntamente
con O_RDWR ni O_WRONLY.
O_APPEND: Sitúa el puntero al final del archivo y permite añadir nuevos datos.
O_CREAT: Crea y abre un archivo para escritura. Si el archivo existe esta operación no se
realiza, es decir este flag no tiene efecto.
Si se necesita abrir el archivo en más de un modo y, siempre que éstos sean compatibles,
puede hacerse utilizando el operador OR (I).
La función open( ) devuelve un valor entero no negativo, el descriptor del fichero, si este se
abrió correctamente. Un valor devuelto igual a –1 indica que se ha producido un error al abrir el
archivo. Si la función open( ) se ejecutó sin error el puntero del fichero, que marca la posición actual
en el fichero, se coloca al comienzo de éste.
- Función close( )
La función close( ) cierra el archivo asociado a una Handle, es decir, cierra el descriptor de
fichero indicado por Handle (Ver formato inferior).
Formato:
int close(Handle)
int Handle;
Ejemplo:
/* --------------------
Programa: fich1.c
-------------------- */
# include <stdio.h>
# include <fcntl.h>
int handle;
main ( )
{
system(“clear”);
handle = open(“datos”,O_CREAT | O_WRONLY);
printf(“\nNumero de handle asociado al\n”);
printf(“fichero \”datos\”: <%d> \n”, handle);
close (handle);
}
El programa anterior abre un archivo, de nombre “datos”, para escritura. El número de handle
devuelto es un número entero positivo mayor que 0, en caso contrario, si se produce un error, retorna
el valor –1.
- Función crat( )
Esta función UNIX, crea un nuevo archivo o rescribe un fichero existente, indicado por
NomFichero.
La función creat() devuelve un entero no negativo, el descriptor de archivo, si no hay error, y
un –1 si éste se produce.
Los datos se introducen, byte a byte, como el agua en un depósito (el buffer o zona de
memoria) que el programador controla. Si uno se descuida podemos salirnos del depósito (buffer).
Respecto a esto, ha de tenerse en cuenta que es posible que no todos los registros tengan el
mismo formato y que éstos pueden variar en bloques con una cadencia determinada. Tampoco le será
difícil averiguar cómo es la variación y adaptar la longitud del buffer cada vez que sea necesario.
Por otro lado, quizá si se pregunta cómo se tratan los datos numéricos, ya que lo que se escribe
y lee son caracteres ASCII. Sencillamente, escribiendo los números como caracteres (dígitos) y
leyéndolos como tales.
Formatos:
#include <stdio.h>
int write(Descriptor, Cadena, Num)
int Descriptor;
char *Cadena;
unsigned int Num;
#include <stdio.h>
int read(Descriptor, Cadena, Num)
int Descriptor;
char *Cadena;
unsigned int Num;
Ambas son funciones UNIX y no han sido incorporadas por el estándar ANSI.
La función white( ) escribe Num de bytes en el archivo definido por Descriptor desde la
posición de memoria apuntada por Cadena. Esta función devuelve el número de bytes realmente
escritos o el valor –1 si se produce un error de escritura.
La función read( ) lee Num de bytes desde el archivo definido por Descriptor hasta la zona de
memoria apuntada por Cadena. Esta función devuelve el número de bytes leídos 0 –1 si se produce un
error de lectura.
Ejemplos:
El siguiente programa lee desde el teclado una cadena de caracteres (no mayor de 100, que es el
tamaño de buffer), terminada con la pulsación simultanea de la tecla <Enter>. El número de caracteres
leídos desde el teclado (num) se escribe en el archivo “datos”. Se cierra el archivo y, posteriormente,
se abre para lectura. Se leen los bytes escritos y se imprimen por pantalla.
/* --------------------
Programa: fich2.c
-------------------- */
# include <fcntl.h>
# define DIEZ “\x1B[10;10f”
System(“clear”);
handle = open(“datos”, O_RDONDLY);
read(handle, buffer, num);
printf(DIEZ);
printf(“%s \n\n\n”,buffer);
close(handle);
}
Ejemplo:
Este segundo ejemplo, crea un archivo de nombre “oscar”, en el cual se graban líneas de texto. El
fichero “oscar” se cierra al teclear “quit” en una línea nueva. Posteriormente se abre el fichero para
visualizarlo línea a línea por pantalla.
/* --------------------
Programa: fich3.c
-------------------- */
/* .. ..
Lectura y escritura usando E/S sin memoria intermedia.
# include <fcntl.h>
# include <stdio.h>
# include <string.h>
main( )
{
char buf[BUF_SIZE];
int fd1,fd2;
system(“clear”);
input(buf,fd1);
close(fd1);
/*---------------------------------------------------------*/
/* Lectura de algunas líneas desde el teclado. */
/* ----------------------------------------- */
input(buf, fd1)
char *bud;
int fd1;
{
register int t;
printf(“Introduce texto, para <Parar> introducir ‘quit’\n”);
printf(“en una nueva línea. \n\n”);
do
{
for (t=0;t<BUF_SIZE,t++)
buf[t]=’\0’;
printf(“: “);
gets(buf); /* Introduce caracteres desde el teclado. */
if (write(fd1,buf, BUF_SIZE) != BUF_SIZE)
{
printf(“Error de escritura. |n”);
exit(1);
}
}
while (strcmp(buf,”quit”));
}
/* ----------------------------------------------------*/
/* PRESENTA EL TEXTO. */
/* ____________________ */
display(buf, fd2)
Char *buf;
int fd2;
{
system(“clear”);
printf(“\n\n\n”);
printf(“ TEXTO TECLEADO. \n”);
printf(“ --------------- \n\n”);
for(;;)
{
if (read(fd2,buf,BUF_SIZE) == 0)
return;
printf(“---->:<%s>:\n”,buf);
}
}
Ejemplo:
Formato:
Int unlink(NombreDeFichero)
Char *NombreDeFichero;
Globalmente, antes y fuera de cualquier función se inicializa el buffer de memoria. Hay que
prever, con esta forma de inicialización, un carácter más (el ‘\0’) que se añadirá automáticamente a la
cadena , siempre que haya espacio disponible.
La función write( ) escribe 100 caracteres en el archivo “datos”. Para acceder otra vez al
principio del mismo, se cierra el archivo y se vuelve a abrir para lectura. Los bytes del archivo se leen
ahora de 10 en 10, hasta alcanzar el fin de archivo, y se imprimen en pantalla con write( ). Se utiliza el
número del dispositivo estándar salida, pantalla, el 2.
/*--------------------
Programa: fich4.c
-------------------- */
# include <fcntl.h>
# include <stdio.h>
main( )
{
int n;
system(“clear”);
unlink(“datos”);
- Función lseek( )
Formato:
#include <stdio.h>
long lseek(Descriptor, Desplazamiento, Origen)
int Descriptor;
long Desplazamiento;
int Origen;
La función lseek( ) es otra función de UNIX original y no está incluida en el estándar ANSI
actual y es similar a la de esta, fseek( ).
- Función tell( )
Formato:
# include <io.h>
long tell(Descriptor)
int Descriptor;
La función tell( ) es otra función UNIX original y es similar a la estándar ANSI ftell( ) y
devuelve la posición actual del puntero al archivo, esta posición se expresa como un número de bytes
desde el comienzo del fichero. Esta función, si se incluye, debe compilarse con el flag –dos.
Ejemplo:
Este programa C, es una utilidad que permite acceder a los distintos bloques de un fichero de forma
directa., para ello utilizamos la función lseek( ) seguida de una función read( ).
/* --------------------
Programa: fich1_di.c
-------------------- */
/* .. ..
Lectura y escritura usando E/S sin memoria
intermedia.
Este programa lee memorias intermedias
usando lseek.
.. .. */
# include <fcntl.h>
# include <stdio.h>
# include <ctype.h> /* Se necesita para isprint( ) */
char buf[SIZE]
void display(int);
main(argc, argv)
int argc;
char *argv[ ];
{
char s[10]M
int fd,sector,numread;
long pos;
system(“clear”);
if (argc !=2)
{
printf(“\n\n Se olvido introducir el nombre del fichero. \n”);
exit(1);
}
do
{
printf(DIEZ);
printf(“\n\n Sector <-1> -> FIN : “);
gets(s);
system(“clear”);
sector = atoi(s); /* coge el numero de sector para leer */
if (sector != -1)
{
pos = (long) (sector*SIZE);
if (lseek(fd,pos,0) != pos)
printf(“\n Error de seek. \n”);
numerad = read(fd, buf, SIZE);
display(numread);
}
}
while (sector >= 0);
close(fd);
}
/* Funcion */
/* ---------- */
void display(numread)
int numread;
{
int i,j;
for (j=0;i<numread/16;i++)
{
for (j=0;j<16;j++)
printf(“%3x”buf[i*16+j]);
printf(“ “);
for (j = 0;j < 16; j++)
{
if (isprint(buf[i*16+j]))
printf(“%c,buf[i*16+j]);
else printf(“.”);
}
printf(“\n”);
}
}
Hexadecimal ascii
20 20 20 20 20 20 20 20 2e 2e a 20 20 20 4c 65 ... Le
63 74 75 72 61 20 79 20 65 73 63 72 69 74 75 72 ctura y escritur
61 20 75 73 61 6e 64 6f 20 45 2f 53 20 73 69 6e a usando E/S sin
20 6d 65 6d 6f 72 69 61 a 20 20 20 69 6e 74 65 Memoria. inte
72 6d 65 64 69 61 2e a a 20 20 20 45 73 74 65 rmedia. Este
20 70 72 6f 67 72 61 6d 61 20 69 6c 75 73 74 72 programa ilustr
61 20 61 6c 67 75 6e 6f 73 20 61 73 70 65 63 74 a algunos aspect
6f 73 20 64 65 6c 20 73 69 73 74 65 6d 61 a 20 os del sistema.
Sector <-1> -1 FIN : 1
Formatos:
La función fup( ) devuelve el número de hanle que se asocia a la próxima corriente que se
abra. Dup( ) devuelve el más bajo descriptor disponible. El nuevo descriptor retornado por dup( ) tiene
en común con el original:
La función dup2( ) hace que un archivo abierto pueda ser referenciado por un
segundo nombre, sin crear un nuevo handle. La función dup2( ) enlaza a un segundo
Handle2 con el archivo asociado a Handle1.
El acceso a los registros del archivo no se modifica por relacionar a éste con un segundo canal
o Handle.
Si se efectúa una llamada a la función dup 2( ) con un handle de un archivo abierto, se cierra
primero el archivo al que estaba asignado la Handle.
/* --------------------
Programa: fich2_di.c
-------------------- */
# include <fcntl.h>
# include <stdio.h>
# define TB_MIN 1
# define TB_MAX 12
main( )
{
handle = open(“datos”,O_CREAT | O_WRONLY);
prox_handle = dup(handle);
system(“clear”);
printf(“\n\nNumero de handle asociado a \”datos\” : <%d>”, handle);
printf(“\nNumero del próximo handle asociado : <%d> \n”, prox_handle);
close(handle);
}
/* --------------------
Programa: fich3_di.c
-------------------- */
/* .. ..
La línea a utilizar para compilar este programa es:
45% cc –compat –lx fich8_di.c
.. .. */
# include <fcntl.h>
# include <stdio.h>
main( )
{
system(“clear”);
handle = open(“datos”,O_CREAT | O_WRONLY);
printf(“\n Numero de handle asociado <%d> \n”, handle);
num = write(handle, buffer, 100);
close(handle);
handle = open(“datos”,O_RDONLY);
dup2(handle, otro_handle);
read(otro_handle, cadena, num);
CAPÍTULO 13
ASIGNACIÓN DINÁMICA DE MEMORIA EN “C”
13.1.- INTRODUCCIÓN
Hasta ahora no nos ha preocupado la forma en la que C guarda la información que se maneja
en la memoria principal de la computadora. Los modelos de memoria en C gestionan ésta de la
siguiente forma.
- Los códigos y los datos inicializado (variables globales y externas) ocupan una zona que se
mantiene fija al principio de la memoria. Esta zona se asigna durante la compilación del
programa.
- La pila ocupa la zona superior de la memoria asignada, teniendo su crecimiento hacia la zona
del montón.
Esta organización anterior puede presentar un grave problema: es posible que en algunas
aplicaciones la pila, en su crecimiento, llegue a ocupar parte de la zona del montón.
Este problema, planteado en los puntos anteriores, se puede solucionar con la asignación
dinámica.
La asignación dinámica se utiliza también para otros usos por ejemplo, cuando se declaran
arrrays de estructuras, el compilador C asigna el espacio de memoria suficiente para almacenar toda la
posible información del array. Este sistema no es eficiente en el caso de arrays grandes en los que no
se usan todos los elementos, desperdiciándose parte de la memoria que no será utilizada.
Todas estas funciones están contenidas en la biblioteca estándar <malloc.h>. Estas funciones
devuelven punteros tipo void.
La función malloc( ) retorna un puntero al primer byte del bloque de memoria de tamaño
solicitado. En caso de no haber memoria libre (del montón) suficiente para asignarla al bloque,
devuelve n puntero NULL. Tiene el siguiente formato:
La función free( ) libera un bloque que previamente había sido asignado mediante malloc( ) o
calloc( ). Tiene el siguiente formato:
Puntero debe contener la dirección del primer byte del bloque. En el caso de que no sea un
puntero utilizado con alguna de las funciones de asignación, lo más probable es que el sistema quede
bloqueado.
Otro efecto que puede ocurrir al utilizar de forma sucesiva malloc( ), APRA asignaciones de
memoria, y free( ), para liberaciones es el de la fragmentación.
A B C D
Si con la orden free( ) liberamos los bloques B y D, el tamaño de la memoria libre es de 80;
sin embargo, no podrá ser asignado ningún bloque mayor de 45, puesto que no se podrá distribuir al
estar el bloque C asignado en medio. De ello se deduce que no es conveniente hacer asignaciones de
bloques de memoria pequeños, sino agruparlos en uno grande.
La función realloc( ) ajusta el tamaño del bloque localizado por el puntero al especificado por
nuevo_tamaño. Tiene el siguiente formato:
Esta función retorna la dirección del nuevo bloque. Puede ser diferente de la dirección del
bloque inicial. Si el bloque no puede ser ajustado retorna NULL.
Todas las funciones vistas se aplican a estructuras dinámicas de datos, tales como colas, pilas,
listas enlazada y árboles binarios, que proporcionan diversas formas de almacenamiento y
recuperación de la información, como veremos a partir de ahora.
13.3.- COLAS
13.3.1 COLA LINEAL
La cola lineal es una técnica de almacenar información organizada de forma que el primer
dato que se introduce es el primero que se recupera (responde a la estructura FIFO, First Input First
Output), de manera que para recuperar un elemento determinado es preciso recuperar los datos
anteriores. Todos los datos que se recuperan se pierden.
Esta estructura tiene su utilidad en los buffers de entrada/salida rápidos y planificados con
organización secuencial.
Vemos un ejemplo de gestión de una cola lineal. Donde una función permitirá introducir los
datos, según el orden en que estos son recibidos (introduce_comerciante( )), otra función permite
extraer los datos próximo_comerciante( )).
...
p_comerciante[1]
*___________________*_____________________*_________________
__*
bloque1 bloque2 bloque3
bloquen = strlen(comerciante)
/*----------------
Programa: cola.c
----------------- */
/*----------------
Ficheros cabecera
----------------- */
#include <malloc.h>
#include <string.h>
#include <stdio.h>
/*--------------------
Sentencias directivas.
--------------------- */
#define MAXIMO 10
#define TRUE 1
#define DOS “\x1B[2;20f”
#define TRES “\x1B[3;20f”
#define CUATRO “\x1B[4;20f”
#define CINCO “\x1B[5;20f”
#define SEIS “\x1B[6;20f”
char *p_comerciante[MÁXIMO];
int numero=0, orden=0;
void introduce_comerciante( );
void proximo_comerciante( );
/* --------- */ /*--------- */
void introduce_comerciante( )
{
char comerciante[10], *puntero_bloque_memoria;
while (numero < MÁXIMO)
{
fflush(stdin);
printf(DIEZ2);
printf(“ “);
printf(ULTIMA);
printf(“<Return> FINALIZAR”);
printf(DIEZ);
printf(“Nombre a añadir a la cola <%d > =-> “, numero +1);
gets(comerciante);
if (*comerciante == 0)
break;
puntero_bloque_memoria=malloc(strlen(comerciante));
if (puntero_bloque_memoria == NULL)
{
printf(“\nEspacio de memoria insuficiente”);
return;
}
else
{
strcpy(puntero_bloque_memoria, comerciante);
p_comerciante[numero] = puntero_bloque_memoria;
numero++;
}
}
}
/* ------- */ /* ------- */
void proximo_comerciante( )
{
if (orden == numero)
{
printf(DIEZ);
printf(“(.)(.)(.) No hay más nombres. (.)(.)(.)”);
numero=0;
orden=0;
return;
}
if (p_comerciante[numero-1] == NULL)
return;
printf(DIEZ);
printf(“Siguiente comerciante[%d]: <%s> :”,orden+1,p_comerciante[orden]);
orden++;
}
/*-----------------
Programa principal
------------------ */
main( )
{
char aux;
while (TRUE)
{
system(“clear”);
printf(DOS);
En este programa primero se deben introducir todos los datos de la cola y posteriormente se
van obteniendo en el mismo orden en que fueron introducidos.
La cola circular evita el problema que presenta la cola lineal de tener que interrumpir el
programa cuando se introduce el máximo de datos permitidos. La solución es hacer que el índice de
dato a introducir (ind_intro_comerciante) se coloque otra vez al principio de la cola, una vez que se
llega al final de ésta. El mismo tratamiento sufrirá el índice del comerciante al extraer los datos.
Con este método se podrá introducir y extraer comerciantes de forma ilimitada (la cola sólo se
llenará cuando ambos índices sean iguales).
/*--------------------
Programa: cola_cir.c
-------------------- */
/*----------------
Fichero cabecera
---------------- */
#include <string.h>
#include <stdio.h>
#include <malloc.h>
/*-------------------
Sentencias directivas
--------------------- */
#define MAXIMO 10
#define TRUE 1
#define DOS “\x1B[2;20f”
#define TRES “\x1B[3;20f”
#define CUATRO “\x1B[4;20f”
#define CINCO “\x1B[5;20f”
#define SEIS “\x1B[6;20f”
#define SIETE “\x1B[7;20f”
#define DIEZ “\x1B[10;25f”
#define DIEZ2 “\x1B[10;49f”
char *p_comerciante[MAXIMO];
Intind_prox_comerciante=0,int_intro_comerciante=0;
Void introduce_comerciante();
Void proximo_comerciante();
/* ------- */ /* ------- */
void introduce_comerciante()
{
char comerciante[30], *puntero_bloque_memoria;
If (ind_intro_comerciante==MAXIMO)
{
if (ind_prox_comerciante != 0)
ind_intro_comerciante = 0;
else
{
fflush(stdin);
printf(DIEZ);
printf(“***---La cola esta llena---***”);
getchar( );
return;
}
}
if(ind_intro_comerciante+1==ind_prox_comerciante)
{
fflush(stdin);
printf(DIEZ);
printf(“***---La cola esta llena---***”);
getchar( );
return;
}
fflush(stdin);
printf(DIEZ2);
printf(“ “);
printf(DIEZ);
printf(“Nombre a añadir a la cola : “);
gets(comerciante);
if (*comerciante == 0)
return;
puntero_bloque_memoria= malloc(strlen(comerciante));
if (puntero_bloque_memoria == NULL)
{
printf(“\nEspacio de memoria insuficiente”);
return;
}
else
{
strcpy(puntero_bloque_memoria, comerciante);
p_comerciante[ind_intro_comerciante] = puntero_bloque_memoria;
ind_intro_comerciante++;
}
}
/* ------- */ /* ------- */
void proximo_comerciante( )
{
fflush(stdin);
if (ind_prox_comerciante == ind_intro_comerciante)
{
printf(DIEZ);
printf(“(.)(.)(.) No hay más nombres. (.)(.)(.)”);
getchar( );
return;
}
if (ind_prox_comerciante == MAXIMO)
{
Ind_prox-comerciante=0;
}
if (p_comerciante[ind_prox_comerciante] == NULL)
return;
printf(DIEZ);
printf(“Siguiente comerciante <%s>“,p_comerciante[ind_prox_comerciante]);
Getchar( );
ind_prox_comerciante++;
}
/*------------------
Programa principal
------------------ */
main( )
{
char aux;
while (TRUE)
{
system(“clear”);
printf(DOS);
printf(“MENU DE GESTION DE UNA COLA CIRCULAR.”);
printf(TRES);
printf(“-----------------------------------“);
printf(CUATRO);
printf(“P – INSERTAR NOMBRES A LA COLA .”);
printf(CINCO);
printf(“S – OBTENER NOMBRES DE LA COLA.”);
printf(SEIS);
printf(“X – SALIR DEL PROGRAMA .......”);
printf(SIETE);
printf(“\t Opción -> “);
fflush(stdin);
switch (toupper(aux=getchar( )))
{
case ‘P’:
introduce_comerciante( );
break;
case ‘S’:
proximo_comerciante( );
break;
case ‘X’:
system(“clear”);
exit(0);
}
}
}
13.4.- PILAS
Las pilas al contrario de las colas, son secuencias de datos en las que la información que se
recupera es justamente la última que se ha almacenado (responden a la estructura LIFO, Las Input
Fiirst Ottput). Un uso de las pilas es almacenar las direcciones de retorno de los programas que, con C,
BASIC, FORTRAN, COBOL, etc. realizan llamadas a subrutinas (funciones en C).
Asignaremos dinámicamente una zona libre de memoria para la pila, según el tamaño máximo
que se prevea. Los datos se almacenarán a partir de la dirección inferior a la superior, sin llegar a
superar el máximo. En el siguiente ejemplo que gestiona una pila la función introduce_datos( ) se
encarga de introducir un dato en la pila. Previamente se verifica que queda zona libre, incrementando
la dirección que podrá ser ocupada por otro dato.
/*----------------
Programa: pila.c
---------------- */
/*-----------------
Ficheros cabecera.
------------------ */
# include <string.h>
# include <stdio.h>
/*---------------------
Sentencias directivas.
--------------------- */
/*------------------
Variables Externas.
------------------- */
int *dirección_pila;
int *dirección_inferior;
int *dirección_superior;
int dato;
/*--------------------------------------
Prototipos de las funciones utilizadas.
--------------------------------------- */
Void introduce-dato(void);
void recupera_dato(void);
/*---------
Funciones.
---------- */
void introduce_dato( )
{
If (dirección_pila > dirección_superior)
{
printf(VEINTE);
printf(“(.) LA PILA ESTA LLENA (.)”);
return;
}
printf(DIEZ);
printf(“DATO A INTRODUCIR : “);
scanf(“%d”,&dato);
*dirección_pila=dato;
dirección_pila++;
}
/*------ */ /* ------ */
void recupera_dato( )
{
dirección_pila--;
if (dirección_pila < dirección_inferior)
{
printf(VEINTE);
printf(“(.) LA PILA ESTA VACIA. (.)”);
return;
}
printf(DIEZ);
printf(“ El dato es = <%d> “, *dirección_pila);
}
/*----------------
Programa principal
----------------- */
main( )
{
char aux;
dirección_pila=(int *)malloc(MÁXIMO * sizeof(int));
If (dirección_pila == NULL)
{
printf(VEINTE);
printf(“ESPACIO DE MEMORIA INSUFICIENTE”);
return;
}
dirección_inferior = dirección_pila;
dirección_superior = dirección_pila + MÁXIMO –1;
while (TRUE)
{
system(“clear”);
printf(DOS);
printf(“MENU DE GESTIÓN DE UNA PILA”);
printf(TRES);
printf(“---------------------------“);
printf(CUATRO);
printf(“- I – Introducir dato. “);
printf(CINCO);
printf(“- R – Recuperar dato.. “);
printf(SEIS);
printf(“- T – Terminar........ “);
printf(SIETE);
printf(“\t Opcion -> “);
fflush(stdin);
switch(toupper(aux=getchar( )))
{
case ‘I’:
introduce_dato( );
break;
case ‘R’:
recupera_dato( );
break;
case ‘T’:
system(“clear”);
exit(0);
}
}
}
Para obtener un dato que ocupe un orden determinado, previamente se deben leer los datos
anteriores (colar) o los posteriores (pilas); es decir, no se pueden realizar accesos aleatorios.
Una lista enlazada es una estructura dinámica de información en la que los datos están
relacionados entre si mediante punteros. Se denomina lista simplemente enlazada si cada elemento
está conectado con el siguiente y lista doblemente encadenada si cada elemento está conectado con el
siguiente y con el anterior.
Tanto en la lista simplemente enlazada como en la lista doblemente enlazada debe existir un
puntero al primer elemento de la lista (Ver figuras inferiores).
Las listas son estructuras complejas de datos. Permiten acceder a un elemento determinado de
forma aleatoria y sin que signifique su destrucción. Como segunda ventaja de las listas es la de poder
operar con datos complejos (listas de estructuras), permitiendo la inserción de nuevos elementos y la
recuperación o el borrado de un elemento especifico.
Puntero_al_primer elemento_de_la_lista
P. a dato2
Dato 1 Dato 2 P. a dato3 Dato 3 P. a dato4
Daton NULL
Puntero_al_primer_elemento_de_la_lista
NULL Dato 1 P.a Dato2 P.a Dato1 Dato 2 P.a Dato3 P.a Dato2 Dato 3 P.a Dato4
Definimos una estructura con las variables necesarias para crear una agenda (Nombre,
Dirección, Teléfono) y con una variable tipo puntero que contenga la dirección del primer byte de la
estructura siguiente.
struct agenda
{
char nombre[30];
char dirección[50];
char teléfono[10];
struct agenda *siguiente;
};
También es necesario definir las variables puntero a esta estructura mediante la instrucción
struct agenda *primero, *nuevo, *índice, dado que vamos a trabajara con las direcciones de los
elementos de la lista.
Cuando se asigne una zona de memoria a cada elemento de la lista, malloc( ) devolverá un
puntero a la dirección del primer byte de la estructura. *Siguiente es el puntero al siguiente elemento
de la lista. La variable puntero primero contendrá la dirección del primer elemento de la lista.
Igualmente las variables puntero nuevo e índice hacen referencia al primer byte de una estructura.
Para hacer referencia a un miembro de la estructura debemos utilizar el operador “.”, que
conecta una variable estructura con uno de sus miembros, pero, como hemos definido variables
puntero a estructura, es necesario redireccionar mediante la expresión (*puntero). Para facilitar este
proceso C proporciona el operador ->, que contenga un puntero con un miembro de la estructura. Por
ejemplo, nuevo -> dirección hace referencia a la variable dirección a través del puntero nuevo.
Como la función malloc( ) retorna una dirección, pero sin informar sobre el tipo de dato, para
asegurarse de que es un puntero a estruct agenda se utiliza el typecast o modelado (struct agenda *).
Una vez obtenido el puntero, se hace preciso enlazar el nuevo elemento de la lista con los
existentes. Primero se comprueba la existencia de algún elemento en la lista; si no lo hay, se asigna el
valor del puntero nuevo al puntero primero. En caso contrario, el nuevo elemento se añade al final de
la lista. El enlace se consigue con índice->siguiente=nuevo.
índice->siguiente = borra->siguiente
Incluimos a continuación el listado del programa “lista.c” que abarca lo comentado en los
punteros anteriores.
/*-----------------
Programa: lista.c
----------------- */
/*-----------------
Ficheros cabecera
----------------- */
#include <string.h>
#include <stdio.h>
/*---------------------
Sentencias directivas
--------------------- */
#define VERDAD 1
#define CUATRO “\x1B[4;10f”
#define CINCO “\x1B[5;10f”
#define SIETE “\x1B[7;10f”
#define OCHO “\x1B[8;10f”
/*---------------------------------------
Prototipos de las funciones utilizadas.
---------------------------------------- */
void intro_fihca( );
void lista_fichas( );
void muestra_ficha( );
void borra_ficha( );
void alamacena_datos( );
void lee_datos( );
void asigna_memoria( );
/*-------------------------------------
Estructura de registro de un cliente.
------------------------------------- */
struct agenda
{
char nombre[30];
char dirección[35];
char teléfono[10];
struct agenda *siguiente;
};
/*--------------------------------------------
Variables puntero al tipo estructura anterior.
------------------------------------------- */
/*----------
Funciones.
---------- */
void intro_ficha( )
{
system(“clear”);
asigna_memoria( );
fflush(stdin);
printf(OCHO);
printf(“ALTA DE UN NUEVO CLIENTE”);
printf(NUEVE);
printf(“------------------------“);
printf(ONCE);
printf(“Nombre del cliente: “);
gets(índice->nombre);
printf(TRECE);
printf(“Dirección de trabajo : “);
gets(índice->dirección);
printf(QUINCE);
printf(“Teléfono de trabajo : “);
gets(índice->teléfono);
índice->siguiente = (struct agenda *) NULL;
/* ------ */ /* ------ */
void lista_fichas( )
{
if (primero == (struct agenda *) NULL)
{
flush(stdion);
system(“clear”);
printf(DIEZ);
printf(“* La lista de clientes esta vacía.”);
printf(ONCE);
prinft(“* Pulsa <Return> *”);
getchar( );
return;
}
índice = primero;
do
{
visu_ficha(índice);
/* ------ */ /* ------ */
viod muestra_ficha( )
{
char inbuf[30]
fflush(stdion);
system(“clear”);
printf(CUATRO);
printf(“ Indroduzca el nombre del cliente: “);
agets(infub);
diplay_ficha(inbuf);
}
/* ------ */ /* ------ */
void borra_ficha( )
{
struct agenda *borra;
char infub[30];
fflush(stdin);
system(“clear”);
printf(CUATRO);
printf(“Nombre del cliente a dar de baja: “);
gets(inbuf);
borra = (struct agenda *) display_ficha(inbuf);
if (borra == (struct agneda *) NULL)
return;
printf(DIECISIETE);
printf(“Dar de baja a este cliente. (s/n) : “);
switch(toupper(getchar( )))
{
case ‘N’:
return;
case ‘S’:
break;
}
if (borra == primero)
primero = primero -> siguiente;
else
{
índice=primero;
while (índice->siguiente != borra)
índice = índice->siguiente;
índice->siguiente = borra->siguiente;
}
free(borra);
}
/* ------ */ /* ------ */
void almacena_datos( )
{
FILE *almacena;
char inbuf[20];
fflush(stdin);
system(“clear”);
printf(DIEZ);
printf(“Grabación de la lista de clientes en disco. “);
printf(ONCE);
printf(“Introduzca el nombre del fichero : “);
gets(inbuf);
/* ------ */ /* ------ */
/* .. ..
Lee una lista de estructura de un fichero en disco,
si ya existe una lista en memoria, la lista leida del
disco se añade al final.
.. .. */
void lee_datos( )
{
FILE *lee;
char inbuf[20];
system(“clear”);
fflush(stdin);
prinft(DIEZ);
printf(“Recuperación de datos de los clientes del disco.”);
printf(ONCE);
printf(“Introduzca el nombre del fichero: “);
gets(inbuf);
if ((lee = fopen(inbuf,”rb”)) == NULL)
{
system(“clear”);
printf(DIEZ);
perror(“Error de acceso en el fichero indicado”);
getchar( );
return;
}
asigna_memoria( );
do
{
índice->siguiente = nuevo;
índice = nuevo;
fread(índice, sizeof(struct agenda); 1, lee);
nuevo = (struct agenda *) malloc (sizeof(struct agenda));
}
while (índice->siguiente != NULL);
fclose(lee);
}
/* ------ */ /* ------ */
void asigna_memoria( )
{
nuevo = (struct agenda *) malloc(sizeof(struct agenda));
if (nuevo == NULL)
{
system(“clear”);
printf(DIEZ);
/* ------ */ /* ------ */
/* ------ */ /* ------ */
}
else
visu_ficha(ficha);
return ficha;
}
/* ------ */ /* ------ */
void visu_ficha(visu)
struct agenda *visu;
{
int aux;
fflush(stdin);
system(“clear”);
printf(SIETE);
printf(“Nombre del cliente : <%-35s>”, visu->nombre);
printf(NUEVE);
printf(“Dirección de trabajo : <%-35s>”, visu->dirección);
printf(ONCE);
/*------------------
Programa principal.
------------------ */
void main( )
{
char aux;
while(VERDAD)
{
system(“clear”);
printf(CUATRO);
printf(“ * Menu clientes *”);
printf(CINCO);
printf(“ --------------------“);
printf(SIETE);
printf(“- I - Introducir un nuevo cliente. -“);
printf(OCHO);
printf(“- L - Listado de todos los clientes. -“);
printf(NUEVE);
printf(“- V - Visualizar datos de un cliente. -“);
printf(DIEZ);
printf(“- B - Baja de un cliente. -“);
printf(ONCE);
printf(“- G - Grabar los datos en disco. -“);
printf(DOCE);
printf(“- R - Leer datos del disco. -“);
printf(TRECE);
printf(“- F - ** FINALIZAR ** -“);
printf(QUINCE);
printf(“ Opción:-> “);
switch(toupper(aux=getchar( )))
{
case ‘I’:
intro_ficha( );
break;
case ‘L’:
lista_fichas( );
break;
case ‘V’:
muestra_ficha( );
break;
case ‘B’:
borra_ficha( );
break;
case ‘G’:
almacena_datos( );
break;
case ‘R’:
lee_datos( );
break;
case ‘F’:
system(“clear”);
exit(0);
}
}
Otra observación sobre esta función es que realiza el enlace del primer elemento almacenado
en el archivo con el último elemento de la lista presente en la memoria (caso de estar trabajando con
una lista). Si no se desea esto, es preciso añadir la sentencia siguiente en la función lee_datos( );
Cada nodo se relaciona (ramifica) con otros nodos siguiendo algún criterio de relación. Estos,
a su vez, se relacionan con otros nodos formando verdaderos subárboles. Cada vez que se ramifica se
dice que se cambia el nivel; el nivel 1 está formado por el nodo raíz y la altura del árbol son las capas
que crecen a partir de la raíz.
Los árboles binarios son aquellos en los que cada nodo sólo puede tener dos ramificaciones:
derecha e izquierda. Tienen especiales aplicaciones porque permiten búsquedas, inserciones y
borrados rápidos. Vemos un ejemplo gráfico de un árbol binario de 3 niveles.
DATO M
P. IZDA P. DCHA
DATO M DATO M
Aunque la representación gráfica de un árbol muestre la estructura que tienen los datos en la
memoria, no hay que olvidar que el almacenamiento real de los elementos se realiza en la memoria
lineal de la computadora.
Cada nodo del árbol se define como una variable mediante la estructura dinámica siguiente:
struct árbol
{
int clave;
.
.
.
struct árbol *izquierda;
struct árbol *derecha;
};
donde clave es la variable que nos indica el valor del nodo, mientras que las variables estructura
izquierda y derecha enlazan con los nodos que tienen un valor de clave menor o mayor,
respectivamente, que la raíz.
La mayoría de las funciones utilizadas pro estas estructuras de datos son recursivas, puesto
que el árbol es una estructura recursiva (cada subárbol es un árbol).
Las operaciones que tengamos que realizar sobre los árboles necesitarán recorrer
secuencialmente cada nodo del mismo siguiendo un orden previamente establecido. Hay tres posibles
ordenaciones, según se efectúe el recorrido de los nodos.
Preorden: Se considera la raíz antes que los subárboles, que se recorren de izquierda a
derecha. El árbol del gráfico se recorrería en el siguiente orden:
MDAKRPX
Orden Central: Se considera primero el árbol más a la izquierda, seguido de la raíz y los
demás subárboles ordenados de izquierda a derecha.
ADKMPRX
AKDPXRM
En la búsqueda de los datos almacenados en árboles binarios se sigue un camino único desde
la raíz al nodo. La función siguiente puede ser utilizada para este fin.
/* ---------- */ /* --------- */
struct árbol *busca(raíz, nodo)
{
struct árbol *raíz;
int nodo;
if (!raíz) /* Árbol vacío */
return raíz;
while (raíz->clave != nodo)
{
if (nodo < raíz->clave)
raíz = raíz->izquierda;
else
raíz = raíz->derecha;
if (raíz == NULL)
break; /* Termina la ejecución. */
}
return raíz;
}
/* -------- */ /* --------- */
Hasta que no se encuentre el nodo buscado se realiza el bucle while recorriendo la izquierda
mientras el valor buscado sea inferior a los pesos que encuentra, o bifurcando hacia la derecha en caso
contrario; una vez localizado retorna el puntero al nodo en cuestión termina la ejecución de la función
en caso de llegar al final de árbol, raíz == NULL. Cuando se utiliza una marca de final de árbol la
condición del bucle while será sustituida por:
El borrado en los árboles binarios consiste en la eliminación del nodo que tenga una clave
determinada. Se pueden presentar varios casos según la posición que ocupe el nodo. Puede tratarse del
nodo raíz o de un nodo terminal. El nodo a borrar puede tener uno o dos subárboles. La función
borrado puede ser la siguiente:
/* --------- */ /* --------- */
/* ---------- */ /* ------------ */
Si borra la raíz, y ésta tiene un solo subárbol, izquierdo o derecho, se elimina el nodo y se
retorna al puntero del subárbol. Si son dos los subárboles, izquierdo y derecho, se recorre la rama más
a la izquierda del subárbol derecho del nodo a borrar, sustituyendo el puntero izquierdo por el derecho,
borrando el nodo y retornando el puntero.
La creación de un árbol binario debe basarse en una función que permita la ordenación según
el valor de una clave, introducida como dato desde la función main( ). La función main( ) tendrá que
contener:
/* --------- */ /* --------- */
main( )
{
struct árbol *crea_árbol( );
raíz = NULL;
char dato;
do
{
printf(“ Entre un dato : “);
gets(&dato);
if (!raíz)
raíz = crea_árbol(raíz, raíz, dato);
else
cra_árbol(raíz, raíz, dato);
}
while (dato);
}
/* -------- */ /* ------- */
El bucle do-while recoge los datos, llamando la primera vez a la función crea_árbol( ) que
retornará un puntero al nodo raíz, el cual permanecerá constante durante toda la ejecución del
programa. La función que va recogiendo los valores de los datos y ejecuta la ordenación es la
siguiente:
/* ----------- */ /* ----------- */
struct árbol *crea_árbo(raíz, aux, dato)
struct árbol *raíz;
struct árbol *aux;
char dato;
{
if (!aux)
{
aux = (struct árbol *) malloc(sizeof(struct árbol));
aux->izquierda = (struct árbol *) NULL;
aux->derecha = (struct árbol *) NULL;
aux->clave = dato;
if (!raíz)
return aux; /* Primer nodo = raíz */
if (dato < raíz->clave)
raíz->izquierda = aux;
else
raíz->derecha = aux;
return aux;
}
if (dato < aux->clave)
crea_árbol(aux,aux->izquierda, dato);
else
if (dato > aux->clave)
crea_árbol(aux,aux->derecha, dato);
}
/* ---------- */ /* ----------- */
La primera vez que se ejecuta esta función será para almacenar la raíz, con lo que los valores
iniciales de raíz y de aux son NULL. En este caso se asigna memoria dinámicamente son malloc( ), se
almacena el dato y los punteros de la izquierda y de la derecha a NULL y se retorna el valor del
puntero de la primera entrada (puntero aux).
El siguiente programa recoge todo lo comentado en los puntos anteriores. Almacena los datos,
ordenándolos según van entrando, en una estructura de árbol. El programa permite borrar un nodo y
visualiza el listado de los datos en función del sistema de ordenación que se elija, Preorden, Central,
Postorden.
/*-----------------
Programa: árbol.c
----------------- */
/*-----------------
Ficheros cabecera
----------------- */
# include <string.h>
# include <stdio.h>
/*---------------------
Sentencias directivas
--------------------- */
# define VERDAD 1
# define SEIS “\x1B[6;10f”
# define SIETE “\x1B[7;10f”
# define NUEVE “\x1B[9;10f”
# define DIEZ “\x1B[10;10f”
# define ONCE “\x1B[11;10f”
# define DOCE “\x1B[12;10f”
# define TRECE “\x1B[13;10f”
# define CATORCE “\x1B[14;10f”
/*--------------------------------------
Prototipos de las funciones utilizadas
-------------------------------------- */
void intro_nodo ( );
void lista_nodo ( );
void borra_nodo ( );
void lista_preorden ( );
void lista_central ( );
void lista_postorden ( );
struct árbol *borra ( );
struct árbol *crea_arbol ( );
/*---------------------
Estructura de un nodo
--------------------- */
struct árbol
{
char clave;
struct árbol *izquierda;
struct árbol *derecha;
};
/*----------------------------
Puntero a la estructura raíz
---------------------------- */
/* --------- */ /* ---------- */
/* ----------- */ /* ----------- */
/* -------- */ /* --------- */
void intro_nodo( )
{
char dato;
fflush(stdin);
do
{
system(“clear”);
printf(DIEZ);
printf(“Tecleame un carácter. “);
printf(ONCE);
printf(“<Return> finalizar-> “);
gets(&dato);
if (!raíz)
raíz = (struct árbol *) crea_árbol(raíz, raíz, dato);
else
(struct árbol *) crea_árbol(raíz, raíz, dato);
}
while (dato);
}
/* -------- */ /* --------- */
void borra_nodo( )
{
char dato;
system(“clear”);
fflush(stdin);
printf(DIEZ);
printf(“Carácter a borrar :-> “);
gets(&dato);
(struct árbol *) borra(raíz, dato);
}
/* ----------*/ /* ---------- */
void lista_preorden(aux)
struct árbol *aux;
{
int orden;
system(“clear”);
if (!aux)
return;
printf(“ %c “, aux->clave);
lista_preorden(aux->izquierda);
lista_preorden(aux->derecha);
}
/* -------- */ /* ---------- */
lsita_central (aux->derecha);
}
/* ----------- */ /* ------------ */
void lista_postorden(aux)
struct árbol *aux;
{
int orden;
system(“clear”);
if (!aux)
return;
lista_postorden(aux->izquierda);
lista_postorden(aux->derecha);
printf(“ %c “,aux->clave);
}
/* -------- */ /* ---------- */
void lista_nodos( )
{
fflush(stdin);
system(“clear”);
printf(SEIS);
printf(“LISTADO DE LOS NODOS.”);
printf(SIETE);
printf(“---------------------“);
printf(NUEVE);
printf(“ – P – Preorden. -“);
printf(DIEZ);
printf(“ – C – Orden Central. -“);
printf(ONCE);
printf(“ – S – Postorden. -“);
printf(TRECE);
printf(“ Opcion -> “);
switch(toupper(getchar( )))
{
case ‘P’:
lista_preorden(raíz);
fflush(stdin);
printf(CATORCE);
printf(“Pulsa <Return> para continuar”);
getchar( );
break;
case ‘C’:
lista_central(raíz);
fflush(stdin);
printf(CATORCE);
printf(“Pulsa <Return> para continuar”);
getchar( );
break;
case ‘S’:
lista_postorden(raíz);
fflush(stdin);
printf(CATORCE);
printf(“Pulsa <Return> para continuar”);
getchar( );
break;
}
}
/*------------------
Programa principal
------------------ */
void main( )
{
while (VERDAD)
{
system(“clear”);
printf(SEIS);
printf(“ MENU DEL PROGRAMA ÁRBOL “);
printf(SIETE);
printf(“ ----------------------- “);
printf(NUEVE);
printf(“ –A – Añadir nodo. “);
printf(DIEZ);
printf(“ – L – Listado de nodos. “);
printf(ONCE);
printf(“ – B – Borrar nodo. “);
printf(DOCE);
printf(“ – T – Terminar. “);
printf(CATORCE);
printf(“ Opción -> “);
switch(toupper(getchar( )))
{
case ‘A’:
intro_nodo ( );
break;
case ‘L’:
lista_nodos ( );
break;
case ‘B’:
borra_nodo( );
break;
case ‘T’:
system(“clear”);
exit(0);
}
}
}