Está en la página 1de 262

CAPTULO 1

Principios Fundamentales
Captulo 1: Principios fundamentales Programacin en C
1.1 INTRODUCCIN
El lenguaje C es actualmente uno de los ms utilizados por aquellos
programadores que buscan la estructura de un lenguaje de alto nivel con la
potencia y eficacia de un lenguaje ensamblador.Es utilizado para prcticamente
cualquier tarea de programacin.
El motivo de este documento es el de complementar los conocimientos tericos
adquiridos a lo largo del curso con ejercicios de tipo prctico que ayuden a
asimilar y comprender todas y cada una de las partes de las que se compone
este lenguaje de programacin.
La materia recogida sigue un orden secuencial. Cada uno de los captulos
contendrn un repaso de teora con datos de inters y un nmero determinado
de ejercicios en los que se requiere un dominio de los apartados anteriores.
1.2 COMPONENTES DE UN PROGRAMA EN C
Todos los programas en C comparten unas caractersticas comunes. Para
familiarizarse con estos elementos se propone el siguiente programa:
/* Primer programa en lenguaje C */
#include "stdio.h"
main()
{
printf("Este es el primer programa en C");
}
Una vez compilado y ejecutado, el programa muestra en pantalla el texto:
Este es el primer programa en C
Los elementos que contiene el programa son de uso comn y a continuacin se
describe brevemente su misin:
/* Primer programa en lenguaje C */
Es un COMENTARIO. En C, los comentarios empiezan con la secuencia /* y
terminan con */. El compilador ignora todo lo que est entre los smbolos de
comienzo y final de comentario.
Se puede poner un comentario en cualquier parte del programa. En C estndar
de ANSI no se pueden anidar (uno dentro de otro) comentarios, como muestra el
siguiente comentario dentro de un comentario, que generar un error de
compilacin:
/* esto es /* un error */ */
Turbo C tiene una opcin de anidar, pero haciendo esto se convierte en no
transportable (el cdigo fuente no se puede llevar a otro compilador de C o
a otro ordenador).
#include "stdio.h"
Representa una DIRECTIVA, es decir una instruccin que va dirigida al
compilador. Indica que debe leer el archivo de cabecera stdio.h y que su
Cap.1-2
Captulo 1: Principios fundamentales Programacin en C
contenido servir para cargar adecuadamente las funciones de librera (rutinas
que ya estn compiladas) utilizadas en el programa.
El nombre de archivo se puede especificar tanto en maysculas como en
minsculas, pero el mtodo tradicional es en minsculas. El archivo de
cabecera STDIO.H contiene, entre otras cosas, informacin relativa a la
funcin de biblioteca printf().
Obsrvese que la directiva #include no termina con un punto y coma. La razn
de esto es que #include no es una palabra clave C que pueda definir una
sentencia.
El archivo fuente que se leer debe encerrarse entre comillas dobles o
parntesis de ngulo, por ejemplo:
#include "stdio.h"
#include <stdio.h>
Si se encierra el archivo entre comillas, se busca el archivo primero en el
directorio de trabajo actual. Si no se encuentra, se busca en cualquier
directorio especificado en la lnea de rdenes. Finalmente, si el archivo
todava no se ha encontrado, el compilador busca los directorios estndar.
En caso de encerrarse el archivo entre parntesis en ngulo, el compilador
buscar primero en el archivo en los directorios que se especifican en la
lnea de rdenes de compilador. Si no se encuentra el compilador busca en los
directorios estndar. En ningn momento el compilador busca el directorio
actual de trabajo.
Es vlido que los "archivos incluidos" tengan directivas # include. Estos
archivos se denominan includes anidados.
Otra directiva utilizada muy frecuentemente es # define que se usa para
definir un identificador y una cadena que el compilador sustituir por el
identificador cada vez que se encuentre en el archivo fuente. El estndar ANSI
se refiere al identificador como nombre de MACRO y al proceso de sustitucin
como sustitucin de macro. El formato general para esta directiva es:
# define identificador cadena_de_macro
No hay punto y coma detrs de esta sentencia. Puede haber cualquier nmero de
espacios entre el identificador y la cadena, pero, una vez que comienza la
cadena, slo puede terminarla una nueva lnea. Ejemplo:
# define MAX 100
Esto hace que el compilador de C sustituya por el valor 100 cada vez que en
archivo fuente se encuentre con el nombre MAX.
main()
Representa una funcin, Todos los programas en C constan de una o ms
funciones, cada una de las cuales contiene una o ms sentencias. En C, una
funcin es una subrutina con nombre que realiza una sola tarea y a la que se
puede llamar desde otras partes del programa. Las funciones son los bloques
constituyentes de C. Una sentencia especifica una accin a llevar a cabo por
el programa. En otras palabras, Las sentencias son las partes del programa que
realmente realizan las operaciones. Las sentencias se encuentran dentro de las
funciones.
Para indicar las funciones se utiliza una notacin convencional que ha llegado
a se estndar en los texto de C. Una funcin tendr parntesis despus del
nombre de la funcin.
Cap.1-3
Captulo 1: Principios fundamentales Programacin en C
Todas las sentencias de C terminan en punto y coma (;). C no reconoce el final
de lnea como un lmite. Esto significa que no hay restricciones sobre la
posicin de las sentencias dentro de una lnea. Adems, se pueden situar dos
o ms sentencias en una lnea.
La forma ms simple de una funcin de C:
nombre_de_funcin()
{
secuencia de sentencias
.........
}
Donde nombre_de_funcin es el nombre de la funcin y la secuencia de
sentencias pueden ser una o ms sentencias. Salvo en escasas excepciones, se
puede llamar a una funcin por el nombre que se quiera. Pero ste se debe
componer slo con letras maysculas y minsculas del alfabeto, los dgitos del
0 al 9 y el carcter subrayado. C es SENSIBLE AL TAMAO, lo que significa que
C diferencia entre letras maysculas y minsculas, es decir Mifunc y mifunc
son nombre distintos para C.
Aunque un programa en C puede contener diversas funciones, la nica funcin
que DEBE tener es main() ("principal"), la funcin main() es la funcin en la
que comienza la ejecucin de un programa. Es decir, cuando el programa
comienza a operar, empiezan ejecutando las sentencias que hay dentro de la
funcin main(), empezando por la primera sentencia que hay despus de la llave
de apertura. La ejecucin del programa termina cuando (conceptualmente) se
alcanza la llave de cierre.
Dependiendo del compilador que se utilice y de como estn configuradas sus
opciones, al compilar el programa nos indicar que no hay valor de retorno de
main(). Es opcional y puede ignorarse. Ms adelante se aprender cmo devolver
un valor desde main().
Otro componente importe de todos los programas de C son las FUNCIONES DE
BIBLIOTECA. El C estndar ANSI especifica un conjunto mnimo de funciones de
biblioteca que se tienen que suministrar con todos los compiladores de C y que
el programa puede utilizar. A esta coleccin de funciones se le conoce
habitualmente como la BIBLIOTECA ESTNDAR DE C. La biblioteca estndar
contiene funciones para llevar a cabo E/S de disco (entrada/salida), manejo
de cadenas de caracteres, operaciones matemticas y mucho ms. Cuando el
programa est compilado, el cdigo para las funciones de biblioteca se aade
automticamente al programa. En la mayora de otros lenguajes como COBOL,
Pascal, estas funciones estn integradas en el lenguaje. La ventaja de
tenerlas como funciones de biblioteca radica en su mayor flexibilidad.
Prcticamente todos los programas de C que se creen utilizarn funciones de
la biblioteca estndar de C.
printf("Este es el primer programa en C");
Una de las funciones de biblioteca ms comunes se llama printf(). Esta es una
funcin de salida de uso general de C. Aunque printf() es muy verstil, su
forma ms simple es:
printf("cadena-a-mostrar");
La funcin printf() muestra en pantalla los caracteres contenidos entre las
comillas dobles de inicio y fin (las comillas dobles no se muestran en
pantalla). En C uno o ms caracteres encerrados entre comillas dobles se llama
CADENA. A la cadena entre comillas que hay entre los parntesis de printf()
se la llama ARGUMENTO. En C, la llamada a una funcin de biblioteca es una
sentencia; por eso debe terminar con un punto y coma.
Para llamar a una funcin, se especifica su nombre seguido de la lista de
Cap.1-4
Captulo 1: Principios fundamentales Programacin en C
argumentos que se va a pasar, entre parntesis. Si la funcin no requiere
ningn argumento, no se especificarn argumentos -y la lista entre parntesis
aparecer vaca-. Si hay ms de un argumento, los argumentos deben estar
separados por comas.
1.3 IDENTIFICADORES
Los operadores manipulan variables y constantes para formar expresiones. Estos
cuatro -variables, constantes, operadores y expresiones- son el abecedario del
lenguaje C.
NOMBRES DE IDENTIFICADORES
El lenguaje C define IDENTIFICADORES como los nombres que se utilizan para
referenciar variables, funciones, etiquetas y otros objetos definidos por el
usuario. En C un identificador puede tener de uno a varios caracteres. El
primer carcter DEBE ser una letra o un subrayado, los restantes caracteres
pueden ser cualquier combinacin de caracteres alfabticos y numricos
(incluido el smbolo de subrayado).
En Turbo C son significativos los 32 primeros caracteres de un identificador.
Esto significa que si dos variables tienen en comn los 32 primeros caracteres
y difieren slo en el 33, Turbo C no las discernir.
El lenguaje C trata las maysculas y minsculas como diferentes. Por ejemplo
contador, Contador y CONTADOR son tres identificadores distintos.
Un identificador no puede ser una palabra clave de C y no debera tener el
mismo nombre de las funciones que se escriben a que estn en la biblioteca de
C.
DECLARACIN DE VARIABLES Y ASIGNACIN DE VALORES
Una VARIABLE es una posicin de memoria con nombre que puede guardar distintos
valores. En C, a diferencia de otros lenguajes de computadora, todas las
variables se deben declarar antes de poder utilizarlas. Una declaracin de
variable tiene un importante propsito: le dice al compilador de C qu tipo
de variable se est utilizando.
C soporta 5 tipos diferentes bsicos:
char, int, float, double, void.
Para declarar una variable se utiliza esta forma general:
tipo nombre_de_variable
Donde tipo es un tipo de dato de C y nombre_de_variable es el nombre de la
variable. Por ejemplo a continuacin se declara que contador es de tipo int:
int contador;
En C una declaracin de variable es una sentencia y debe terminar con un punto
y coma.
Se puede declarar ms de una variable del mismo tipo utilizando una lista
separada por comas. Por ejemplo:
float x, y, z;
En C se pueden dar valores a la mayora de las variables a la vez que se
declaran poniendo un signo igual y una constante despus del nombre de la
variable, el formato general de la inicializacin es:
Cap.1-5
Captulo 1: Principios fundamentales Programacin en C
tipo nombre_de_variable = constante;
As como hay distintos tipos de variables, hay distintos tipos de constantes:
Un carcter constante se especifica colocando el carcter entre comillas
simples. Los enteros se especifican con nmeros enteros. Los valores en punto
flotante deben incluir un PUNTO DECIMAL. Ejemplos de declaracin de variable
seran:
char ch = A;
int primero = 0 ;
float balance = 123.23 ;
Para asignar un valor a una variable, se pone su nombre a la izquierda
de un signo de igual. A la derecha del signo "igual" se pone el valor que se
quiere dar a la variable. En C, una operacin de asignacin es una sentencia;
por tanto, debe terminar con un punto y coma. La forma general de una
sentencia de asignacin es:
nombre_de_variable = valor;
num = 100;
CONSTANTES
En C las constantes se refieren a los valores fijos que el programa no
puede alterar.
Las constantes en C pueden ser cualquier tipo bsico. Las constantes de
caracteres estn encerradas entre comillas simples. Por ejemplo, a y % son
constantes de caracteres.
Las constantes enteras son nmeros sin componente de fraccin. Por
ejemplo 10 y -100. Las constantes de punto flotante requieren el uso del punto
decimal seguido por el componente fraccional. Ejemplo 3.14.
C permite especificar constantes enteras en hexadecimal u octal en vez
de en decimal si se prefiere. Una constante en hexadecimal debe comenzar por
0x (un cero seguido de una x). Una constante octal comienza con un cero.
Ejemplos:
int hex = 0xFF ; /* 255 en decimal */
int oct = 011 ; /* 9 en decimal */
C soporta otro tipo de constante adems de las de tipos de datos
predefinidos: la cadena ("string"). Una cadena es un conjunto de caracteres
que se encierran entre comillas dobles. Por ejemplo "esto es una cadena".
No confundir las cadenas con los caracteres. Se encierra una constante
de un slo carcter con comillas simples; por ejemplo v, sin embargo, "v"
es una cadena que contiene un slo carcter.
El uso de comillas simples para encerrar todas las constantes de
caracteres funciona para la mayora de los caracteres imprimibles, pero
algunos, como retorno de carro, es imposible de introducir por teclado. Por
esta razn, C proporciona las constantes especiales de caracteres de barra
invertida que se muestran a continuacin:
Cap.1-6
Captulo 1: Principios fundamentales Programacin en C
Cdigo Significado
\b Retroceso
\f Alimentador de hojas
\n Nueva lnea
\r Retorno de carro
\t Tabulador horizontal
\" Doble comilla
\ Comilla simple
\0 Nulo (para cadenas)
\\ Barra invertida
\v Tabulador vertical
\a Alerta
\N Constante octal ( N es constante octal)
\x Constante hexadecimal
Se usa un cdigo de barra invertida de la misma forma que cualquier otro
carcter. Ejemplo: ch = \t;.
VARIABLES GLOBALES Y LOCALES
Hay dos lugares bsicos donde se declara una variable: dentro de una
funcin y fuera de todas las funciones. Estas variables se llaman variables
LOCALES y variables GLOBALES, respectivamente.
Las variables locales (declaradas dentro de una funcin) slo las pueden
referenciar las sentencias que estn dentro de esa funcin. No son conocidas
fuera de su propia funcin. Una de las cosas ms importantes que hay que
entender sobre las variables locales es que solamente existen mientras se est
ejecutando la funcin en la que estn declaradas. Es decir, una variable local
se crea al llamar a su funcin y se destruye cuando se sale por lo que no
pueden mantener sus valores entre llamadas.
A diferencia de las variables locales, las variables globales son
conocidas a lo largo de todo el programa y las puede utilizar cualquier trozo
de cdigo del programa. Adems, mantendrn su valor durante toda la ejecucin
del programa. Las variables globales se crean declarndolas fuera de cualquier
funcin. Por ejemplo:
Cap.1-7
Captulo 1: Principios fundamentales Programacin en C
# include "studio.h"
int max; /* Esta es una variable global */
main()
{
max = 10;
f1();
}
f1()
{
int i; /* Variable local */
for(i = 0; i < max; i++)
printf("%d",i);
}
Aqu, tanto main() como f1() aluden a la variable global max. La
variable local i es definida dentro de la funcin f1().
En C, una variable local y una variable global pueden tener el mismo
nombre. Por ejemplo, ste es un programa vlido:
# include "stdio.h"
int contador; /* contador como variable global */
main()
{
contador = 10 ;
f1() ;
printf ("contador en main(): %d\n", contador") ;
}
f1()
{
int contador; /* contador como variable local */
contador = 100;
printf("contador en f1() : %d\n", contador);
}
El programa muestra esta salida de pantalla.
contador en f1() : 100
contador en main() : 10
En main(), la referencia a contador es a la variable global. Dentro de
f1(). Tambin hay definida una variable local llamada contador. Cuando se
encuentra la sentencia de asignacin dentro de f1(), el compilador mira
PRIMERO para ver si hay una variable local llamada contador. Puesto que la
hay, se utiliza la variable local, no la global del mismo nombre. Es decir,
cuando las variables locales y globales comparten el mismo nombre, el
compilador utilizar siempre la variable local.
Las variables globales son muy tiles cuando muchas funciones del
programa utilizan los mismos datos. Sin embargo, se deberan utilizan
variables locales donde se pueda, ya que un uso excesivo de variables globales
tiene algunas consecuencias negativas. Primero, las variables globales
Cap.1-8
Captulo 1: Principios fundamentales Programacin en C
utilizan memoria todo el tiempo que el programa est en ejecucin, no slo
cuando se necesitan. En situaciones en las que la memoria es un recurso
escaso, esto podra suponer un problema. Segundo, la utilizacin de una
variable global donde servira una variable local hace a una funcin menos
general, ya que confa en algo que se debe definir fuera de s misma.
Se deben entender dos aspectos importantes de las variables. En primer
lugar, dos variable globales no pueden tener el mismo nombre. Si lo tienen,
el compilador no sabra qu variable usar. Intentar declarar dos variables
globales con el mismo nombre va a hacer que el compilador d un mensaje de
error. En segundo lugar, una variable local en una funcin puede tener el
mismo nombre que una variable local en otra funcin sin conflicto. La razn
para esto es que el cdigo y los datos en la funcin estn completamente
separados del de la otra funcin. Simplemente, las sentencias dentro de una
funcin no tienen conocimiento de las de otras funciones. Sin embargo debe
recordarse que no deben tener el mismo nombre dos variables en la misma
funcin.
TIPOS DE DATOS
Todas las variables en C se deben declarar antes de usarlas. Es
necesario porque el compilador debe saber qu tipo de datos es una variable
antes de poder compilar cualquier sentencia en la que se use. En C hay cinco
tipos bsicos:
carcter (character)
entero (integer)
punto flotante (floating-point)
doble punto flotante (double-floating-point)
sin valor (valueless)
Las palabras claves para declarar variables de estos tipos son
respectivamente:
char, int, float, double y void
En la tabla siguiente se presenta el tamao y el rango de cada tipo de
dato de C para el IBM PC.
Tipo Ancho en bit Rango
char 8 -128 a 127
int 16 -32768 a 32767
float 32 3.4E-38 a 3.4E+38
double 64 1.7E-308 a 1.7E+308
void 0 sin valor
Se usan variables del tipo char para guardar caracteres ASCII de 8 bits
como A, B o C. Adems, se puede usar una variable char como un entero
"pequeo" -128 a 127 y en lugar de un entero cuando la situacin no requiere
nmeros mayores. Las variables de tipo int pueden guardar cantidades enteras
que no requieran parte fraccional. Se usan variables de este tipo para
controlar los bucles y las sentencias condicionales. Se incluyen variables de
los tipos float y double en programas que necesiten componente fraccional o
cuando la aplicacin requiera nmeros grandes que pueden almacenar. Un double
en C puede almacenar un nmero mucho mayor.
Cap.1-9
Captulo 1: Principios fundamentales Programacin en C
MODIFICADORES DE TIPO DE DATOS DE C
Los tipos de datos bsicos excepto el tipo void se pueden modificar
utilizando modificadores de tipo de C para que se ajusten ms adecuadamente
a las necesidades especficas surgidas en un programa. Estos modificadores de
tipo son:
signed
unsigned
long
short
El modificador de tipo precede al nombre del tipo. Por ejemplo, esto
declara un entero long:
long int i;
signed.- Utilizado para especificar un tipo de datos de caracteres con
signo (mdulo y signo) en notacin de complemento a dos
1
en tipos de datos
char e int. Ejemplo:
signed char variable; -> -128 a 127 /* Redundantes puesto que por
signed int variable; -> -32768 a 32767 defecto son con signo */
unsigned.- Seala al compilador que no debe considerar el bit de signo
como tal y usar todos los bit en las operaciones aritmticas. Esto tiene el
efecto de doblar el tamao del valor entero posible. Las variables sin signo
solamente pueden contener valores positivos. Puede aplicarse a variables de
tipo char e int. Ejemplo:
unsigned char variable; -> 0 a 255
unsigned int variable; -> 0 a 65535
short.- Declara enteros cortos
2
. Ejemplo:
short int variable; -> -32768 a 32767
long.- Declara variables int o double de doble longitud. Cuando se
aplica a un int dobla la longitud en bits, del tipo de base que se modifica.
Cuando long se aplica a double prcticamente dobla la precisin.
long int variable; -> -2.147.483.648 a 2.147.483.647
long double variable; -> 3,4 E-4932 a 1,1 E+4932 (vara segn el PC)
Los modificadores unsigned y signed se pueden utilizar en combinacin
con long y short. dentro de los enteros (int). Ejemplo:
unsigned short int variable; -> 0 a 65535
1
Los nmeros negativos se presentan utilizando el mtodo de complemento a dos. En este mtodo, todos los bit del nmero
estn invertidos y se aade 1 a este nmero. El indicador del signo es el bit de mayor ("1" para n negativo).
El complemento a dos facilita a la CPU la realizacin de operaciones aritmticas.
2
En teora, el modificador short reduce a la mitad el tamao de un short int, por eso si nuestro compilador utiliza
enteros de 32 bits un short int tendr una longitud de 16 bits. Sin embargo puede que no tenga efecto en nuestro entorno. La
mayora de los compiladores de C de PCs utiliza enteros de 16 bits, ya que este es el tamao de palabra de la familia de
procesadores 8086. El estndar ANSI de C establece que el menor tamao aceptable para un int es de 16 bits. Tambin establece
que el menor tamao de un short int es de 16 bits. Por eso en muchos entorno entornos no hay diferencia entre un int y un short
int.
Cap.1-10
Captulo 1: Principios fundamentales Programacin en C
unsigned long int variable; -> 0 a 4.294.967.295
signed short int variable; -> -32768 a 32767
signed long int variable; -> -2.147.483.648 a 2.147.483.647
C permite usar una notacin abreviada para declarar enteros unsigned,
short o long sin int (se asume por defecto). Ejemplo:
unsigned x; /* declaran variables enteras sin
unsigned int x; signo */
1.4 OPERADORES
Un operador es un smbolo que le dice al compilador que realice
manipulaciones matemticas o lgicas especficas.
C tiene tres clases generales de operadores:
- Aritmticos
- Relacionales y Lgicos
- Manipuladores de bits
OPERADORES ARITMTICOS
Los operadores aritmticos son:
- Resta y menos unario (monario)
+ Suma
* Multiplicacin
/ Divisin
% Mdulo divisin
++ Incremento unario (monario)
-- Decremento unario (monario)
Menos unario (un operando) multiplica su operando por -1. As cualquier
nmero precedido por el signo menos cambia el signo del nmero.
Los operadores +, -, / y * funcionan de la misma manera en C que en la
mayora de los lenguajes de computadora. Pueden utilizarse con cualquiera de
los tipos de datos.
Cuando se aplica / a un entero o carcter, la computadora truncar
cualquier resto; Por ejemplo, 10/3 ser igual a 3 en la divisin entera.
El operador mdulo de la divisin % recoge el resto de una divisin
entera sin embargo, no se puede utilizar % sobre los tipo float o double.
Dos operadores utilsimos que no se encuentran en otros lenguajes de
Cap.1-11
Captulo 1: Principios fundamentales Programacin en C
computadora son los operadores de incremento ++ y decremento --.
El operador ++ aade 1 a su operando y el -- le resta uno. Por ejemplo:
x++ ; /* equivale a x = x + 1 */
x-- ; /* equivale a x = x - 1 */
Estos operadores pueden preceder o seguir su operando:
x++ ; o ++x ;
x-- ; o --x ;
Hay diferencia cuando se usa en una operacin. Cuando un operador
incremento o decremento precede a su operando, C realiza la operacin de
incremento o de decremento antes de usar el valor del operando. Cuando el
operador sigue al operando, C usa el valor del operando antes de incrementar
o decrementarlo. Ejemplo:
x = 10 ;
y = ++x ; /* y vale 11 porque la variable x incrementa primero y despus
x = 10 ; se le asigna a la variable y */
y = x++ ; /* y = 10 porque 1 asigna el valor y despus incrementa */
En ambos casos el valor final de x es 11, la diferencia es cuando lo
hace.
La prioridad de los operadores aritmticos es:
Ms alta ++ --
- (unario)
* / %
Ms baja + -
La computadora evala de izquierda a derecha los operadores con el mismo
nivel de prioridad. Se puede usar parntesis para alterar el orden de
evaluacin. El lenguaje trata todos los parntesis de la misma manera que
prcticamente todos los lenguajes: fuerza sus operaciones, o un conjunto de
ellas, a tener un nivel de prioridad mayor.
El uso de parntesis redundantes o adicionales no provocar errores o
ralentizacin de la ejecucin de un programa.
OPERADORES RELACIONES Y LGICOS
Los operadores relacionales comparan 2 valores y devuelven un resultado
verdadero o falso, basado en la comparacin. Los operadores lgicos agrupan
valores de verdadero/falso. En C, verdad es cualquier valor distinto de cero,
mientras que falso es cero.
Cap.1-12
Captulo 1: Principios fundamentales Programacin en C
Los operadores relacionales y lgicos son:
RELACIONALES LGICOS
> Mayor que && AND
>= Mayor o igual que || OR
< Menor que ! NOT
<= Menor o igual que
== Igual que
!= Distinto que
Se puede aplicar los operadores relacionales a cualquier tipo bsico de
datos. Por ejemplo, este fragmento visualiza el mensaje "mayor que" porque un
"B" es mayor que "A" en la secuencia de referencia ASCII:
...
ch1 = A ;
ch2 = B ;
if (ch2 > ch1) printf ("mayor que");
...
Los operadores lgicos se usan para soportar las operaciones bsicas
lgicas AND, OR y NOT de acuerdo con la siguiente tabla de verdad que usa "1"
para verdad y "0" para falso.
A B A AND B A OR B NOT A
0 0 0 0 1
0 1 0 1 1
1 0 0 1 0
1 1 1 1 0
OPERADOR &&.- El resultado es verdadero si los dos operandos son
verdadero.
OPERADOR ||.- El resultado es verdadero si al menos uno de los operandos
es verdadero.
OPERADOR !.- El resultado es verdadero si el operando el falso y
viceversa.
Los operadores lgicos y relaciones tienen una prioridad ms baja que
los operadores aritmticos. Esto quiere decir que una expresin como:
10 + contador > a + 12
Se evala como si se hubiera escrito (10 + contador) > (a + 12).
Cap.1-13
Captulo 1: Principios fundamentales Programacin en C
Se puede agrupar cualquier nmero de operaciones relacionales utilizando
operandos lgicos. Por ejemplo:
variable1 > max || !(max == 100) && variable2 <= elemento
La prioridad relativa de los operadores relacionales y lgicos es:
Ms alta !
> >= < <=
== !=
&&
Ms baja ||
Como con las expresiones aritmticas, se pueden usar parntesis sin
alterar el orden natural de la evaluacin en una expresin relacional o
lgica.
Se usan operadores relaciones y lgicos para soportar las sentencias de
control de programa, que incluyen todos los bucles y sentencias condicionales.
OPERADORES A NIVEL DE BIT
C contiene diversos operadores especiales que incrementan enormemente
su poder y flexibilidad especialmente para su programacin a nivel del sistema
que realizan sus operaciones bit a bit.
Como C fu diseado para reemplazar el lenguaje ensamblador en la
mayora de las tareas de programacin, tena que poseer la habilidad de
soportar todas (o al menos muchas) operaciones que pueden realizarse en
lenguaje ensamblador. Las operaciones sobre bits se refieren a la
comprobacin, configuracin o desplazamiento de los bits reales contenidos en
un byte o una palabra. Estos operadores trabajan con los tipos caracter y
entero.
La lista de estos operadores es:
& AND
| OR
^ OR EXCLUSIVA (XOR)
~ Complemento a 1 (NOT) (unario)
>> Desplazamiento a la derecha
<< Desplazamiento a la izquierda
Los AND, OR y el complemento a 1 (NOT) se rigen por las mismas tablas
de verdad que gobiernan sus equivalente lgicos, excepto que funcionan a nivel
bit a bit.
Las operaciones sobre bits ms frecuentes encuentran aplicaciones a
controladores de dispositivos -como programas de modems, rutinas de archivo
de disco y rutinas de impresora- ya que se pueden usar las operaciones sobre
Cap.1-14
Captulo 1: Principios fundamentales Programacin en C
bits para enmascarar ciertos bits, como el de paridad
3
.
Generalmente, las operaciones sobre bits AND, OR y XOR aplican sus
operaciones directamente sobre cada bit en la variable individualmente.
Producen un resultado basado en la comparacin de los bits correspondientes
de cada operando.
Por ejemplo, la funcin siguiente lee primero un carcter desde el
puerto de comunicaciones serie (RS-232) usando la funcin (de Turbo C)
bioscom() y despus pone el bit de paridad a cero. Se puede usar la funcin
bioscom()
4
para acceder a los puertos serie asncronos en un IBM PC o
compatible.
# include "bios.h"
...
char coge_car_desde_puerto()
{
char = ch;
ch = bioscom (2, 0, 0); /* coge un carcter desde COM1 */
return(ch & 127);
}
El operador AND (&) activa un bit si los dos bits que se comparan son
"1". Ejemplo "A" & 127:
1 1 0 0 0 0 0 1 (contiene una "A" con paridad)
&
0 1 1 1 1 1 1 1 127 en binario
0 1 0 0 0 0 0 1 "A" sin paridad
El operador OR (|) activa un bit (bit = "1") si al menos uno de los bits
que se comparan es "1". Ejemplo 128 | 3:
1 0 0 0 0 0 0 0 128 en binario
|
0 0 0 0 0 0 1 1 3 en binario
1 0 0 0 0 0 1 1 resultado
3
Se usa el bit de paridad para confirmar que el resto de bits en el byte permanece sin cambios. Es usualmente el bit
ms alto de cada byte.
4
BIOSCOM: I/O COMUNICACIONES RS-232
int bioscom(int cmd, char abyte, int port) /* prototipo en bios.h */
Valores de cmd:
0.- Asigna parmetros de comunicaciones (con abyte).
1.- Situa un byte (abyte).
3.- Recibe un carcter (valor de retorno en los 8 bits de menor peso)
4.- Retorno del estado
port: 0 para COM1, 1 para COM2, etc.
Los 8 bits de mayor peso del valor de retorno contiene los bits de estado.
El valor de los 8 bits de menor peso depende del cmd especificado.
Cap.1-15
Captulo 1: Principios fundamentales Programacin en C
Un OR EXCLUSIVO (XOR) pondr activo un bit si y solo si los bits que se
comparan son diferentes. Ejemplo 127^120:
0 1 1 1 1 1 1 1
^
0 1 1 1 1 0 0 0
0 0 0 0 0 1 1 1 resultado
La tabla de verdad de una puerta XOR es:
A B A XOR B
0 0 0
0 1 1
1 0 1
1 1 0
Los operadores de desplazamiento >> y <<, mueven todos los bits en una
variable a la derecha o izquierda respectivamente un nmero de pasos
especificado. El formato general de la sentencia de desplazamiento a la
derecha es:
variable >> n de posiciones de bit
Y el formato general de desplazamientos a la izquierda es:
variable << n de posiciones de bit
Como las sentencias desplazan bit en un sentido, la computadora trae
ceros en otro. Recordar que un desplazamiento no es una rotacin: los bits
desplazados en un extremo no vuelven al otro. Se pierde y los ceros trados
los reemplazaran.
Las operaciones de desplazamiento pueden ser muy tiles cuando se
codifican entradas de dispositivos externos como convertidores D/A
(Digital/Analgicos) y cuando se lee informacin de estado. Ejemplo:
x Cada vez que se ejecute Valor decimal de x
char x;
x = 7;
x << 1; 0 0 0 0 0 1 1 1 7
x << 3; 0 0 0 0 1 1 1 0 14
x << 2; 0 1 1 1 0 0 0 0 112
x >> 2; 1 1 0 0 0 0 0 0 192
x >> 1; 0 1 1 0 0 0 0 0 96
x >> 2; 0 0 0 1 1 0 0 0 24
El operador complemento a 1 (~) invertir el estado de cada bit en la
variable especificada. Todos los bit que contiene "1" se ponen a "0", todos
los "0" se ponen a "1".
Cap.1-16
Captulo 1: Principios fundamentales Programacin en C
Ejemplo:
ch = 7 ; /* ch = 0 0 0 0 0 1 1 1
2
*/
ch = ~ch ; /* ch = 1 1 1 1 1 0 0 0
2
*/
NOTACIN SIMPLIFICADA EN SENTENCIAS DE ASIGNACIN
C tiene una taquigrafa especial que simplifica la codificacin de
ciertos tipos de sentencias de asignacin. Ejemplo:
x = x + 10 ;
Se puede escribir en lenguaje C como:
x += 10 ;
El par de operadores += le dice al compilador que asigne a x el valor
de s mismo ms 10.
Esta notacin funcionar para todos los operadores binarios (2
operandos) en C. El formato general de esta taquigrafa es:
variable op= expresin ;
OPERADOR COMA ","
Se usa el operador coma para encadenar diversas expresiones.
Esencialmente, su efecto es provocar una secuencia de operaciones a realizar.
Cuando se usa en la parte derecha de una sentencia de asignacin, el valor de
la expresin entera es el valor de la ltima expresin de la lista separada
por comas, por ejemplo:
despus de que la computadora ejecute:
y = 20;
x = ( y = y - 5 , 30 / y ) ;
x tendr el valor 2 ya que el valor original 20 de y se reduce en 5 y
despus ese valor divide a 30, dando 2 como resultado.
En algunos casos se puede pensar que el operador coma tiene el mismo
sentido que el trmino "y", como en la frase << hacer esto y esto >>.
PARNTESIS Y PARNTESIS CUADRADOS
El lenguaje C considera operadores a los parntesis "()" y a los
parntesis cuadrados "[]". Los parntesis hacen el trabajo de incrementar la
precedencia de las operaciones que estn dentro de las mismas. Los parntesis
cuadrados realizan el indexamiento de los arrays (que se discutirn ms
adelante). Es importante anotar que la mayora de los lenguajes de computadora
no consideran operadores a estos smbolos.
RESUMEN DE PRECEDENCIA
La tabla siguiente lista la precedencia de todos los operadores de C.
Observar que todos los operadores, excepto los unarios y ?, se asocian de
izquierda a derecha.
Cap.1-17
Captulo 1: Principios fundamentales Programacin en C
Ms alta () [] -> .
! ~ ++ -- -
5
(tipo) *
6
&
7
sizeof (unarios)
* / %
+ -
<< >>
< <= > >=
== !=
&
^
|
&&
||
?
= += -= *= /= etc.
Ms baja ,
1.5 EXPRESIONES
Los operadores, constantes y variables constituyen expresiones. Una
expresin en C es cualquier combinacin vlida de estas piezas. Como la
mayora de las expresiones tienden a caer en las reglas generales de lgebra,
se dan por sabidas. Sin embargo a diferencia de muchos otros lenguajes de
computadora, C permite mezclar diferentes tipos de datos en una misma
expresin.
A continuacin se hace un estudio sobre la conversin de tipo en
expresiones y en asignaciones.
CONVERSIN DE TIPO EN EXPRESIONES
Cuando se mezclan constantes y variables de diferentes tipos en una
expresin, C las convierte en el mismo tipo. El compilador C convertir todos
los operadores al tipo de operando ms grande en una operacin segn la base
de esta operacin, tal como se describe en estas reglas de conversin de tipo:
1) Todos los char y short int se convierten en int. Todos los float en
double.
2) Para todo par de operandos, lo siguiente ocurre en secuencia:
Si uno de los operandos es un long double, el otro se convierte a long
double. Si uno de los operandos es double, el otro se convierte a double. Si
5
unario, utilizado para la inversin de signo (por -1)
6
Puntero
7
Direccin de una variable
Cap.1-18
Captulo 1: Principios fundamentales Programacin en C
uno de los operandos es float el otro operando se convierta a float. Si uno
de los operandos es unsigned long el segundo se convierte a unsigned long. Si
uno de los operandos es long, el otro se convierte a long. Si uno de los
operandos es unsigned el otro se convierte a unsigned.
Despus que el compilador aplique estas reglas de conversin, cada parte
de operandos ser del mismo tipo, y el resultado de la operacin ser del tipo
de los operandos. Ejemplo de conversin de tipos:
char v_char ;
int v_int ;
float v_float ;
double v_double ;
resultado = (v_char / v_int) + (v_float * v_double) - (v_float + v_int)
conversin 1 -> int -> double -> float
resultado parcial -> int -> double
conversin 2 -> double
resultado parcial -> double -> float
conversin 3 -> double
resultado final double
CONVERSIN DE TIPO EN ASIGNACIONES
En una sentencia de asignacin de la que el tipo del lado derecho
difiere del de la izquierda, el tipo de la derecha se convierte al de la
izquierda.
Cuando el tipo del lado izquierdo es mayor que el tipo del lado derecho,
este proceso no origina problemas. Sin embargo, cuando el tipo del lado
izquierdo es menor que el tipo de la derecha, se puede producir una prdida
de datos. Por ejemplo, este programa muestra -24:
# include "stdio.h"
main()
{
char ch;
int i;
i = 1000 ;
ch = i ;
printf("%d", ch);
}
Cap.1-19
Captulo 1: Principios fundamentales Programacin en C
La razn de esto radica en que slo los 8 bits menos significativos de
i se copian en ch. Dado que esta clase de conversin del tipo de asignacin
no es un error de C. no se recibir mensaje de error.
Una razn por la que se cre C fue para reemplazar el lenguaje
ensamblador; por tanto debe permitir toda clase de conversiones de tipo. Por
ejemplo, en algunos casos puede que slo se quieran los bit de menor peso de
una variable, y esta clase de asignacin permite obtenerlas fcilmente.
OBSERVACIONES:
- La conversin de entero a carcter o long int a int (segn ordenador)
a travs de una asignacin se eliminan los bit ms significativos (8 bit en
el primer caso y 16 en el segundo).
- La conversin de un long double a double o double a float pierde se
precisin (redondeo).
- Cuando se convierte de un valor en punto flotante a un valor entero
se pierde la parte fraccional.
- La conversin de un int en un float no aadir ni exactitud ni
precisin.
OPERADOR DE MOLDEADO (CASTS)
Se puede forzar una expresin a ser de un tipo especfico, usando la
construccin llamada casts. El formato general de un casts es:
(tipo) expresin
Donde tipo es uno de los tipos estndar de datos de C.
Por ejemplo, puede que se quiera utilizar un valor en punto flotante
para un clculo, pero que se le quiera aplicar el operador mdulo (solamente
se puede utilizar sobre valores enteros). Una solucin es la creacin de una
variable entera para utilizar en la operacin de mdulo y asignarle el valor
de la variable en punto flotante cuando llegue el momento. Sin embargo, esta
es una solucin poco elegante. El otro modo de solventar el problema es
utilizando un molde, que origine un cambio de tipo temporal. Por ejemplo:
...
float f;
f = 100.2 ;
/* imprimir f como un entero */
printf ("%d", (int) f) ;
...
Aqu el molde hace que el valor de f se convierta a un int.
1.6 CARACTERSTICAS BSICAS DE LAS FUNCIONES
Las funciones son la verdadera base de C. Como ya conoce, todas las
sentencias de accin tiene que aparecer dentro de una funcin. Este apartado
trata diversos aspectos importantes de las funciones, aunque ms adelante se
profundizar ms en ellas.
Cap.1-20
Captulo 1: Principios fundamentales Programacin en C
FUNCIONES CON ARGUMENTOS
Un argumento de una funcin es un valor que se pasa a la funcin en el
momento que se realiza la llamada. Se pueden crear funciones que toman
argumentos. Por ejemplo:
/* Ejemplo de los argumentos de una funcin */
# include <stdio.h>
main()
{
mul(10,11);
}
mul(a, b)
int a, b;
{
printf("%d", a * b);
}
La declaracin mul() pone dentro del parntesis que sigue al nombre de
la funcin las variables que recibirn los valores pasados a mul().
Las funciones que no tomen argumentos no necesitan variables, as que
el parntesis estar vaco.
En el ejemplo anterior, cuando se ha llamado a mul() los valores 10 y
11 se pasan (copian) en a y b.
Es importante mantener dos trminos claros. Primero, el trmino
ARGUMENTO se refiere al valor usado para llamar a la funcin. El trmino
PARMETRO FORMAL se refiere a la variable de una funcin que recibe el valor
de los argumentos en la llamada.
De hecho, las funciones que toman argumentos se llaman funciones
parametrizadas.
En las funciones de C, los argumentos siempre se separan por comas.
La distincin importante que hay que entender es que la variable
utilizada como argumento en una llamada de funcin no tiene que ver con el
parmetro formal que recibe su valor.
Debe recordarse que el tipo de argumento que se utiliza para llamar a
una funcin deber del mismo tipo que el parmetro formal que recibe ese
argumento. Por ejemplo, no debera intentarse llamar a mul() con argumentos
en coma flotante.
Existen dos formas de declarar los parmetros de una funcin:
.- Declaracin clsica
.- Declaracin moderna
Cuando se invent el lenguaje C, se utilizaba el denominado mtodo
clsico que tras la estandarizacin del lenguaje se modific el modo de
declaracin de argumentos denominndose declaracin moderna, actualmente la
mayora de compiladores de C (entre ellos Turbo C) mantienen los dos formatos,
por lo que cumplen fielmente el estndar ANSI para C (aunque se recomienda el
formato moderno). La razn de que sea importante conocer el formato clsico
es porque existen literalmente millones de lneas de cdigo C que se pueden
Cap.1-21
Captulo 1: Principios fundamentales Programacin en C
utilizar. Adems muchos de los programas publicados en libros y revistas
utilizan este formato porque funciona con los compiladores -incluidos los
antiguos-. Vase la diferencia entre los dos formatos:
.- Declaracin clsica: La declaracin clsica de parmetros de funcin
consta de dos partes: una lista de parmetros, que va entre parntesis despus
del nombre de la funcin y las declaraciones de parmetros reales, que van
entre el parntesis cerrado y la llave de apertura de la funcin. El formato
general de la definicin de parmetros clsica aparece de esta manera:
tipo nombre_de_funcin (parm1, parm2..., parmN)
tipo parm1;
tipo parm2;
...
tipo parmN;
{
Cdigo de la funcin
}
Por ejemplo la siguiente funcin aparece en su formato clsico:
float f(a, b, ch)
int a, b;
char ch;
{
...
}
Observar que en la forma clsica puede existir ms de un parmetro en
la lista despus del nombre de tipo.
.- Declaracin moderna: La principal diferencia con el formato clsico
es que todos los parmetros de funciones deben incluir entre los parntesis
tanto el tipo de variable como el nombre. Esto quiere decir que la lista de
declaracin de parmetros de una funcin adquiere este formato general:
tipo nombre_de_funcin(tipo nom_var1, tipo nom_var2,...,tipo nom_varN)
Como ejemplo la declaracin anterior en su formato moderno:
float f(int a, int b, char ch)
Sin embargo, la siguiente declaracin es incorrecta porque cada
parmetro debe incluir su propio nombre:
f(int x, y, float z) /* declaracin incorrecta */
FUNCIONES QUE DEVUELVEN VALORES
En C, una funcin devuelve un valor a la rutina que la llama usando la
palabra clave return. Para ilustrarlo, puede reescribirse el programa anterior
que imprime el producto de dos nmeros. Obsrvese que este valor se asigna a
una variable situando la funcin en la parte derecha de la sentencia de
asignacin:
Cap.1-22
Captulo 1: Principios fundamentales Programacin en C
/* Un programa que utiliza -return- */
# include <stdio.h>
main()
{
int respuesta;
respuesta = mul(10 , 11);
printf("la respuesta es %d \n", respuesta);
}
/* Esta funcin devuelve un valor */
mul(int a , int b)
{
return a * b ;
}
El valor devuelto por la sentencia return llega a ser el valor de mul()
en la rutina que la llama.
Usando la sentencia return sin ningn valor se puede volver de una
funcin. Adems se puede usar ms de un return en una funcin.
Hay tipos diferentes de variables, y por tanto tipos diferentes de
valores devueltos por una funcin. El tipo que la rutina mul() devuelve es int
por defecto. La forma general de una funcin es:
tipo nombre_de_funcin(lista_de_parmetros)
{
sentencias
}
Aqu tipo especifica el tipo de valor que devuelve una funcin. Una
funcin puede devolver cualquier tipo de dato excepto un array. Si no hay un
especificador del tipo de datos, entonces el compilador de C automticamente
supone que la funcin devuelve un entero. En otros palabras, int es el tipo
implcito cuando no aparece un especificador de tipo. Ese es el motivo de que
no se hayan tenido que utilizar declaraciones explcitas de tipo en las
funciones que se escribieron anteriormente.
Cuando una funcin devuelve un tipo diferente de un int, se debe
declarar explcitamente. Esta funcin, por ejemplo, devuelve un double:
/* calcular el volumen de un cubo */
double (double S1, double S2, double S3).
{
return S1 * S2 * S3;
}
Antes de poder utilizar una funcin que devuelva un valor que no es
entero, debe informarse al compilador sobre el tipo de valor de retorno. Si
no se hace, el compilador generar el cdigo suponiendo que se est
devolviendo un entero. Hay dos modos de informar al compilador sobre el tipo
de valor de retorno de una funcin antes de su compilacin:
A) Mtodo tradicional (obsoleto)
B) Prototipo de funcin
Cap.1-23
Captulo 1: Principios fundamentales Programacin en C
MTODO TRADICIONAL
El mtodo tradicional de informar al compilador sobre el tipo de valor
de retorno consiste en especificar, cerca del principio del programa, el tipo
y el nombre de la funcin, seguidos de parntesis de apertura y cierre y un
punto y coma. Por ejemplo esta sentencia:
double volumen();
Le indica al compilador que volumen() devolver un double. El siguiente
programa utiliza volumen(). Obsrvese donde se declara la funcin:
# include <stdio.h>
double volumen(); /* informa al compilador sobre volumen() */
main()
{
double vol;
vol = volumen(12.2, 5.67, 9.03);
printf("volumen = %f", vol);
}
/* Esta funcin calcula el volumen de un cubo */
double volumen(double S1, double S2, double S3)
{
return S1 * S2 * S3 ;
}
Si una funcin no devuelve ningn valor el compilador y el compilador
es compatible con el C del estndar ANSI, puede declararse la funcin como
void. Esto le indica al compilador que la funcin no devuelve ningn valor e
impide que se utilice en la parte derecha de una sentencia de asignacin. El
siguiente programa utiliza una funcin void:
# include <stdio.h>
void mensaje();
main()
{
mensaje();
}
void mensaje()
{
printf("Esto es un mensaje");
}
La funcin main() puede devolver un valor entero al sistema operativo
. Este valor de retorno se interpreta generalmente como un cdigo de
terminacin. Un valor de retorno de 0 indica una terminacin satisfactoria.
Cualquier otro valor significa que el programa termin a causa de un error.
Si no se especifica ningn valor de retorno, la mayora de los compiladores
devuelven automticamente el valor 0. Aunque no es necesario, muchos
programadores declaran main() como void cuando no se utiliza un valor de
retorno explcito. No es necesario declarar main() por adelantado ya que no
le llama ninguna otra funcin en el programa.
Cap.1-24
Captulo 1: Principios fundamentales Programacin en C
PROTOTIPO DE FUNCIN
El segundo modo de informar al compilador sobre el valor de retorno de
una funcin es utilizando un prototipo de funcin. El prototipo ampla el
mtodo descrito en el apartado anterior, declarando tambin el nmero y tipos
de los argumentos de la funcin. Los prototipos tambin permiten que C
encuentre e informe sobre cualquier conversin ilegal de tipo entre el tipo
de argumentos utilizados para llamar a una funcin y la definicin de tipo de
sus parmetros. Los prototipos tambin facultan al compilador para que informe
cundo el nmero de argumentos de una funcin no es el mismo que el nmero de
parmetros declarados en la funcin.
La versin original de C no soportaba prototipos de funcin. Los aadi
el comit de estandarizacin del ANSI C, y se consideran como una de las
mayores ampliaciones hechas al lenguaje C.
Aqu se muestra la forma general de definicin de un prototipo de
funcin:
tipo nombre_de_funcin(tipo nombre_de_parmetro 1, tipo
nombre_de_parmetro 2,...;tipo nombre_de_parmetro N);
Aqu aparece el programa de clculo de volumen desarrollado en el
apartado anterior utilizando un prototipo de funcin:
# include <stdio.h>
/* este es un prototipo de volumen() */
double volumen(double S1, double S2, double S3);
void main()
{
double vol;
vol = volumen(12.2, 5.67, 9.03);
printf("Volumen: %f", volumen);
}
/* Calcula el volumen de un cubo */
double volumen(double S1, double S2, double S3)
{
return S1 * S2 * S3 ;
}
Puede que los prototipos no parezcan importantes en programa tan
pequeos como este. Sin embargo, al aumentar los programas, los prototipos
ayudarn a evitar errores.
Cuando el comit de estandarizacin de ANSI C aadi los
prototipos de funcin a C, se tuvieron que resolver dos problemas menores de
compatibilidad entre la versin antigua de C y la versin ANSI C relativos a
los prototipos. La primera cuestin fue cmo gestionar el mtodo de
declaracin tradicional, que no utiliza una lista de argumentos. El estndar
ANSI especifica que cuando se produce una declaracin de funcin si una lista
de argumentos, no se indica nada sobre los argumentos de la funcin. Esto
lleva a una pregunta: cmo se escribe el prototipo de una funcin que no toma
argumentos? Por ejemplo, esta funcin muestra simplemente una lnea de puntos:
Cap.1-25
Captulo 1: Principios fundamentales Programacin en C
void line()
{
int = i;
for( 1=0 ; i < 80 ; i++ )
printf (".") ;
}
Si se intenta utilizar lo que viene a continuacin como un prototipo,
no funcionar, ya que el compilador pensar que se est utilizando el mtodo
de declaracin tradicional:
void line();
El comit ANSI resolvi este problema ampliando el uso de la palabra
clave void. Cuando una funcin no tiene parmetros, su prototipo utiliza void
dentro de los parntesis. Por ejemplo, aqu aparece el prototipo adecuado para
line();
void line(void);
Este le indica al compilador que la funcin no tiene parmetros, y
cualquier llamada a dicha funcin que tenga parmetros es un error. Sin
embargo, tambin tiene que asegurarse de utilizar void cuando se define la
funcin. Por ejemplo, line() debe tener este aspecto:
void line(void)
{
int i;
for( i = 0 ; i < 80 ; i++ )
printf(".") ;
}
Aunque no es necesario, por razones de consistencia muchos programadores
declaran main() como sigue:
void main(void)
Este enfoque tambin se utilizar a partir de ahora. Sin embargo, como
pronto se ver main() puede tener argumentos.
Un segundo asunto relacionado con los prototipos es el modo en que
afectan a las conversiones de tipo automticas de C. Debido a algunas
caractersticas del entorno en que se desarroll C, cuando se llama a una
funcin que no tiene prototipo, todos los caracteres se convierten en enteros
y todos los float en double. Sin embargo, estas conversiones de tipo parece
que violan el propsito de los prototipos. La solucin a este problema es que
cuando existe un prototipo, los tipos especificados en el prototipo se
mantienen, y no se producir ninguna conversin de tipo.
Los prototipos de funcin ayudan a escribir los programas de forma ms
sencilla, ya que facilitan que se llame a las funciones en los programas con
sus tipos y nmeros de argumentos correctos.
Valores devueltos.- Como se dijo, todas las funciones, excepto las que
se declaran de tipo void, devuelven un valor. Este valor es definido
explcitamente por una sentencia return, o es 0 si no se usa tal sentencia.
As, mientras no se declara una funcin como void, se puede usar como un
operando en cualquier expresin vlida de C. Por tanto, cada una de las
siguientes es vlida en C:
x = abs(y);
if(max(x,y) > 100) printf("mayor");
for(ch = getchar(); isdigit(ch);....);
Cap.1-26
Captulo 1: Principios fundamentales Programacin en C
Sin embargo, una funcin no puede ser el objeto de una asignacin. Una
sentencia como
swap(x, y) = 100 ; /* sentencia errnea */
es errnea, el compilador de C lo indicar como un error y no compilar un
programa que contenga tal sentencia.
Aunque todas las funciones que no son de tipo void devuelven valores,
las funciones que se escribirn habitualmente sern de tres tipos. El primero
es simplemente de clculo. Se disea esta funcin especficamente para
realizar las operaciones sobre sus argumentos y devolver un valor basado en
esa operacin.
El segundo tipo de funciones manipula informacin y devuelve un valor
que indica simplemente si esta manipulacin sali bien o fall.
El ltimo tipo de funciones no tiene un valor de retorno explcito. En
esencia, la funcin es estrictamente procedural y no produce valor.
Si no se especifica una asignacin, entonces la computadora simplemente
descarta el valor devuelto. Considerar el siguiente programa que usa mul():
# include <stdio.h>
main()
{
int x, y, z;
x = 10;
y = 20;
z = mul(x, y); /* 1 */
printf("%d", mul(x,y) ); /* 2 */
mul(x, y); /* 3 */
}
mul(int a, int b)
{
return a*b;
}
La lnea /* 1 */ asigna el valor devuelto por mul() a z. En la lnea
/* 2 */, el valor devuelto no est realmente asignado, sino que printf() lo
usa. Finalmente, el la lnea /* 3 */ el valor devuelto se pierde porque no lo
asigna a otra variable ni lo usa como parte de una expresin.
Ejemplo de la utilizacin de funciones de usuario:
Cap.1-27
Captulo 1: Principios fundamentales Programacin en C
PROGRAMA C EN CDIGO FUENTE
COMENTARIO
/* PEDIDO.C Un ejemplo de las partes bsicas
de un programa C */
DIRECTIVAS DEL PREPROCESADOR
#include <stdio.h>
#include <stdlib.h>
CUERPO DEL PROGRAMA
PROTOTIPOS DE FUNCIN
float calc_sub_total( int cantidad, float precio );
float calc_tax( float sub_total );
PROGRAMA PRINCIPAL
main()
{
char nombre[30];
char producto[30];
int cantidad;
float precio;
float sub_total;
float tax;
clrscr();
printf( "\n\nIntroduzca su apellido => " );
scanf( "%s", nombre );
printf( "\n\nIntroduzca el nombre de un producto => " );
scanf( "%s", producto );
printf( "\n\nIntroduzca la cantidad y el precio => " );
scanf( "%d %f", &cantidad, &precio );
sub_total = calc_sub_total( cantidad, precio );
tax = calc_tax( sub_total );
clrscr();
printf( "\n\nInformacin del pedido\n\n" );
printf( "Nombre: %s\n", nombre );
printf( "Producto pedido: %s\n\n", producto );
printf( "Cantidad solicitada: %d\n\n", cantidad );
printf( "Precio por unidad: %3.2f\n", precio );
printf( "Sub-total: %3.2f\n", sub_total );
printf( "Impuestos: %3.2f\n", tax );
printf( "Total: %3.2f\n", ( sub_total + tax ) );
}
FUNCIN DE USUARIO 1
float calc_sub_total( int cantidad, float precio )
{
float sub_total;
sub_total = precio * cantidad;
return sub_total;
}
FUNCIN DE USUARIO 2
float calc_tax( float sub_total )
{
const float tax_rate = 0.06;
float tax;
tax = sub_total * tax_rate;
return tax;
}
1.7 CONSOLA DE ENTRADA/SALIDA EN C
La consola de E/S se refiere a las operaciones se producen entre el
teclado y la pantalla de la computadora.
En C no hay instrucciones propias de E/S como en otros lenguajes. En su
lugar existen bibliotecas que contienen las funciones que suplen esta
definicin.
Cap.1-28
Captulo 1: Principios fundamentales Programacin en C
Hay que recordar que las funciones de librera deben tener declarado su
archivo de cabecera (.h) correspondiente.
FUNCIONES BSICAS DE E/S
Archivo de cabecera Entrada Salida
<stdio.h>* getchar() putchar()
scanf() printf()
<conio.h>**
getch() clrscr()*
getche() * slo Turbo C
kbhit()
*Librera estndar de C, todos los compiladores
**Librera de consola no estndar, las funciones indicadas estn en:
.- Compilador Turbo C
.- Compilador Microsoft C
ENTRADA/SALIDA SIMPLE
Las funciones ms sencillas de entrada/salida de consola son las
referentes a la lectura y escritura de caracteres sobre la consola: A
continuacin se describen brevemente.
para la LECTURA de un carcter desde teclado:
getchar().- Acepta un carcter y lo imprime por pantalla. Su prototipo
es:
int getchar(void);
Tiene el inconveniente de esperar a que se pulse la tecla de retorno
(< ) para que el dato sea enviado a la computadora. Este efecto es muy
engorroso en entornos interactivos.
getche().- Espera a pulsar una tecla y despus devolver su valor. La
funcin tambin hace "eco" de la tecla pulsada automticamente en la
pantalla. Su prototipo es:
int getche(void);
getch().- Es una variacin de getche(); funciona de la misma manera
excepto que getch() no hace "eco" de los caracteres tecleados.
kbhit().- Esta funcin se utiliza para determinar si se ha pulsado una
tecla o no. Si el usuario ha pulsado una tecla, esta funcin devuelve
"verdadero" (distinto de 0), pero no lee el carcter. Si hay una
pulsacin de tecla esperando se puede utilizar una de las funciones
vistas anteriormente. Si no hay pendiente ninguna pulsacin de tecla,
kbhit() devuelve "falso". Su prototipo es:
int kbhit(void);
Cap.1-29
Captulo 1: Principios fundamentales Programacin en C
Un ejemplo para la utilizacin de kbhit() es:
# include <conio.h>
int tecla_pulsada, ch;
main()
{
tecla_pulsada = kbhit();
if(tecla_pulsada = 0)
ch = getch;
}
Es muy til cuando se quiere permitir que el usuario interrumpa una
rutina sin forzar realmente al usuario a responder continuamente a un
indicador como "continuar?".
Para la VISUALIZACIN de un carcter:
putchar().- La funcin putchar() muestra un slo carcter en la
pantalla. Aunque su parmetro se declara de tipo int, la funcin lo
convierte en un unsigned char. Su prototipo de funcin es:
int putchar(int ch);
clrscr().- Limpia la pantalla en modo texto. Su prototipo de funcin
es:
void clrscr(void);
ENTRADA/SALIDA CON FORMATO
Adems de las sencillas funciones de E/S de consola descritas
anteriormente, la biblioteca de C estndar contiene dos funciones que realizan
la entrada y salida formateada en los tipos de datos predefinidos, que
incluyen caracteres, cadenas y nmeros: printf() y scanf(). El trmino
formatear se refiere al hecho de que estas funciones pueden leer y escribir
datos en diversos formatos que estn bajo control. La funcin printf() se
utiliza para visualizar en pantalla y scanf(), el complemento de printf(),
para introducir datos por teclado. Estas funciones se examinan con detalle.
printf().- La funcin printf() su prototipo es:
int printf(char* cadena de control, lista de argumentos);
Este prototipo de funcin est en el fichero de cabecera stdio.h. La
CADENA DE CONTROL consta de 2 tipo de elementos:
1 Los caracteres que se imprimirn en la pantalla.
2 rdenes de formato que definen la manera en que se visualizarn los
argumentos.
Debe haber el mismo nmero de argumentos que de rdenes de formato y
ambos deben encajar en orden (emparejados de izquierda a derecha).
En otras palabras, en la funcin printf(), la cadena de control contiene
los caracteres que se visualizarn en la pantalla y las rdenes que le dicen
a printf() cmo visualizar el resto de los argumentos.
Se pueden poner las rdenes de control de formato en cualquier lugar de
Cap.1-30
Captulo 1: Principios fundamentales Programacin en C
la 1 cadena de caracteres. Cuando se llama a printf(), sta rastrea la cadena
de control. La funcin slo imprime los caracteres regulares que aparecen.
Cuando encuentra una orden de formato printf() la memoriza y la usa cuando
imprime los argumentos apropiados.
Las rdenes de formato de printf() son:
Cdigo formato
%c Un slo carcter (en el argumento entre comillas simples)
%d Entero decimal con signo
%i Idem
%e Notacin cientfica ("e" minscula)
%E Notacin cientfica ("E" mayscula)
%f Punto flotante
%g Utiliza %e o %f el que sea ms corto
%G Utiliza %E o %f el que sea ms corto
%o Octal sin signo
%s Cadena de caracteres (en el argumento entre dobles comillas)
%u Entero decimal sin signo
%x Hexadecimal sin signo (letra minscula)
%X Hexadecimal sin signo (letra mayscula)
%p Muestra un puntero
%n El argumento asociado es un puntero a entero que se le
asigna el n de caracteres escritos hasta el momento
%% Imprime el carcter "%"
%hd Muestra un short
%ho Muestra un short
%hx Muestra un short
%hu Muestra un short unsigned int
%ld Muestra un long
%lo Muestra un long
%lx Muestra un long
%lf Muestra un double
%le Muestra un double
%lg Muestra un double
%Ld Muestra un long double
Cap.1-31
Captulo 1: Principios fundamentales Programacin en C
%Lf Muestra un long double
%Le Muestra un long double
%Lg Muestra un long double
La funcin printf() permite incluir dentro de la cadena de caracteres
las constantes del carcter barra invertida de C (ver apartado de CONSTANTES),
resumidos a contnuacin:
\n Nueva lnea
\t Tabulador horizontal
\" Doble comilla
\\ Barra invertida
\N Constante octal ( N es el n octal)
\xN Constante hexadecimal (N es el n hexadecimal)
Las rdenes de formato (excepto %%, %p y %c) pueden tener modificadores
que especifican el ancho del campo, el n de lugares decimales y el indicador
de justificacin a la izquierda.
Aqu se muestra la forma general de un especificador de formato. Los
elementos opcionales se muestran entre corchetes.
%[-][longitud_mnima_de_campo][.][precisin]especificador de formato
Un entero puesto entre el signo % y el orden de formato acta como un
especificador mnimo de ancho de campo. Este completa la salida con blancos
o ceros para asegurar que AL MENOS es la longitud mnima. Si una cadena o
nmero es mayor que el mnimo, se imprimir completa, incluso si desborda el
mnimo. El relleno por defecto se hace con espacios. Si se desea rellenar con
ceros, poner cero antes del especificador de ancho de campo. Por ejemplo:
%05d
Rellenar una serie de 5 dgitos con ceros hasta un total de cinco.
Para especificar el nmero de puestos decimales impresos para un nmero
en punto flotante, poner un punto decimal seguido por el nmero de decimales
que se desean visualizar despus del indicador de ancho de campo. Por ejemplo:
%10.4d
Visualizar un nmero que tendr al menos 10 caracteres de largo y
cuatro posiciones decimales (si hay ms trunca). Cuando se aplica un formato
como este a cadenas o enteros, el nmero que sigue al punto especifica la
longitud mxima del campo. Por ejemplo:
%5.7s
Visualizar una cadena con al menos 5 caracteres de largo y no menos de
7. Si la cadena es ms larga que el ancho mximo del campo, la computadora
truncar los caracteres desde el final.
Por defecto, toda salida es justificada a la derecha: si el ancho del
campo es mayor que los datos impresos, se pondr en el lado derecho del campo.
Se puede forzar que la informacin se justifique a la izquierda poniendo un
signo menos directamente despus de %.
Cap.1-32
Captulo 1: Principios fundamentales Programacin en C
Por ejemplo:
%-10.2f
Justificar a la izquierda un nmero en punto flotante con dos
posiciones decimales en un campo de 10 caracteres.
Con printf() se puede sacar virtualmente cualquier tipo de formato, a
continuacin se presenta algunos ejemplos:
printf("2 + 2"); x = 2;
Visualiza: 2 + 2 y = 3;
printf("%d", 2+2); z = 10;
Visualiza: 4 a = (y * z) / x;
x = 10; printf("%d", a);
printf("%d", x); Visualiza 15
Visualiza: 10 x = 2;
printf("%-5.2f", 123.234); y = 3;
Visualiza: |123.23| z = 10;
printf("%5.2f", 3.234); printf("%d",(y * z) / 2);
Visualiza: | 3.23| Visualiza: 15
printf("%10s","hola"); printf("%-10s","hola");
Visualiza: | hola| Visualiza: |hola |
printf("5.7s","123456789"); printf("\a");
Visualiza: |1234567| Visualiza: emite pitido
printf("%s %d", "esto es una cadena", 100);
Visualizar: esto es una cadena 100
printf("esto es una cadena %d", 100);
Visualiza: esto es una cadena 100
printf("nmero %d es decimal, %f es float", 10, 110.789);
Visualiza: nmero 10 es decimal, 110.789 es float
scanf()
La funcin scanf() es una rutina de entrada de propsito general. Puede
leer todos los tipos de datos predefinidos y automticamente los convierte en
el formato interno adecuado. Es el reverso de printf(). El formato general de
scanf() es:
int scanf(char *cadena de control, lista de argumentos);
Cap.1-33
Captulo 1: Principios fundamentales Programacin en C
El prototipo de scanf() est en stdio.h la cadena de control consta de
tres clasificaciones de caracteres:
A) Especificadores de formato
B) Caracteres de espacio en blanco
C) Caracteres de no espacio en blanco
A) Los especificadores de formato de entrada son precedidos por un signo
% y le dicen a scanf() qu tipos de datos se leen a continuacin. Estos
cdigos se listan en la tabla adjunta.
B) Un carcter de espacio en blanco en la cadena de control provocar
que scanf() lea, pero no almacene, cualquier nmero (incluido el cero) de
caracteres de espacio en blanco hasta el primer carcter de no espacio en
blanco.
C) Un carcter de no espacio en blanco provoca que scanf() lea y
descarte el carcter que "encaja". Por ejemplo:
"%d,%d"
Fuerza a scanf() a descartar una coma despus de leer un entero y
posteriormente leer otro entero. Si no se encuentra el carcter especificado,
terminar scanf() de forma incorrecta.
Cdigo significado
%c Lee un slo carcter
%d Lee un entero decimal
%i Idem
%e Lee un n en punto flotante
%f Idem
%g Idem
%o Lee un n octal sin signo
%s Lee una cadena de caracteres
%u Lee un entero sin signo
%x Lee un n hexadecimal sin signo
%p Lee un puntero
%n Recibe un valor entero igual al nmero de caracteres ledos
hasta el momento
%[] Inspecciona un juego de caracteres
Por ejemplo, si se desea leer un entero en la variable cont se debe
utilizar la siguiente llamada:
scanf("%d", &cont);
Cap.1-34
Captulo 1: Principios fundamentales Programacin en C
Todas las variables usadas para recibir valores a travs de scanf()
deben pasarse por sus direcciones (&cont seala la direccin de memoria donde
se guardar el valor de la variable cont). Esto significa que todos los
argumentos deben ser "apuntados" por sus direcciones. Esta es la manera de
crear una "llamada por referencia" y permite alterar el contenido de un
argumento.
Las cadenas se leern en arrays de caracteres, el nombre del array
(tabla), sin ningn ndice, es la direccin del primer elemento del array.
Suponiendo que la direccin es el nombre de un array de caracteres, para leer
una cadena en la tabla address, usa:
scanf("%s", address);
En este caso, address ya es un "puntero" y no necesita precederse por
el operador &.
A los especificadores %d, %i y %u los puede modificar h (%hd, etc)
cuando se introducen los datos en una variable short y l (%li, etc) cuando se
introduce una variable long.
Los especificadores %e, %f y %g son equivalentes. Todos leen nmeros en
coma flotante representados bien en notacin cientfica o bien en notacin
decimal estndar. Sin modificar introducen un valor en una variable float. Se
pueden modificar como en los casos anteriores pero utilizando l cuando se
utilizan datos en un double. Para leer un long double, se modifican con un L.
Los datos escritos en el teclado DEBEN estar separados por espacios,
tabuladores o nuevas lneas. La puntuacin como comas, punto y coma y
similares no cuentan como separadores. Ejemplo:
scanf("%d%d", &r, &s);
Aceptar una entrada por teclado como 10 20, pero falla con 10, 20. Como
printf(), los cdigos de formato de scanf() encajan en orden con las variables
que reciben la entrada en una lista de argumentos.
Un asterisco"*" puesto detrs de % y antes del cdigo de formato leer
datos del tipo especificado, pero suprimir su asignacin. As:
scanf("%d%*c%d", &x, &y);
Dada la entrada 10/20, pondr el valor 10 en x, descarta el signo de
dividir y da a y el valor de 20. Esto puede ser muy til cuando se introduce
informacin que contiene caracteres innecesarios.
Se puede utilizar scanf() para que lea una cadena utilizando el
especificador %s pero probablemente no lo har. Este es el motivo: cuando
scanf() introduce una cadena, detiene su lectura de la cadena al encontrar el
primer espacio en blanco. (un espacio en blanco es tanto un espacio, como un
tabulador o un salto de lnea). Por ejemplo: para leer entradas como estas en
una cadena:
Esto es una cadena
Puesto que hay un espacio despus de "Esto", scanf() detiene la entrada
de la cadena en ese punto. Ese es el motivo por el que generalmente se utiliza
gets() para introducir cadenas.
El especificador %p introduce la direccin de memoria utilizando el
formato que termina el entorno de la mquina. El especificador %n indica a
scanf() que asigne a la variable apuntada por el correspondiente argumento el
nmero de caracteres ledos desde la secuencia de entrada hasta el punto en
el que se encuentra %n.
Cap.1-35
Captulo 1: Principios fundamentales Programacin en C
A %n lo puede modificar l o h para que asigne su valor a una variable
long o short.
Una caracterstica importante de scanf() se llama conjunto de
inspeccin. Un especificador de conjunto de inspeccin se crea poniendo una
lista de caracteres entre corchetes. Por ejemplo, aqu hay un especificador
de conjunto de inspeccin que contiene las letras "VEM".
%[VEM]
Cuando scanf() encuentra un conjunto de inspeccin, comienza a leer los
caracteres y los asigna al array de caracteres al que apunta el argumento
correspondiente de conjunto de inspeccin. Tan pronto encuentre un carcter
que no forma parte del conjunto de inspeccin, scanf() detiene la lectura de
este especificador y pasa a cualquier otro de la cadena de control.
Se puede especificar un rango en un conjunto de inspeccin utilizando
el guin "-". Por ejemplo, este conjunto de inspeccin especifica los
caracteres de la A a la Z.
%[A-Z]
Cuando el conjunto de inspeccin es muy largo, a veces es ms fcil
especificar lo que no forma parte del conjunto de inspeccin. Para hacer esto,
se precede al conjunto con un "^" (acento circunflejo). Por ejemplo:
%[^0123456789]
Cuando scanf() encuentra este conjunto de inspeccin, leer cualquier
carcter excepto los dgitos del 0 al 9.
Se puede especificar una longitud mxima de campo para todos los
especificadores excepto %c, cuya longitud es siempre de un carcter, y %n al
que no se aplica el concepto. La longitud mxima de campo se especifica como
un entero sin signo, y precede inmediatamente al carcter especificado de
formato. Por ejemplo, esto limita a 20 caracteres la longitud mxima de una
cadena asignada a str:
scanf("%20s", str);
Si la corriente de entrada (informacin introducida por teclado) fuera
ms grande de 20 caracteres, entonces la llamada posterior a entrada empezara
donde se dej esta llamada. Por ejemplo, si se introduce
ABCDEFGHIJKLMNOPQRSTUVWXYZ
como respuesta a la anterior llamada, scanf(), slo los 20 caracteres -o hasta
la "T"- tendran que recogerse en str debido al tamao especificado. Esto
significa que los caracteres restantes "UVWXYZ", no se han usado todava y
permanece en el buffer del lnea (memoria intermedia utilizada por el PC para
contener los datos introducidos por teclado). Si se hace otra llamada a
scanf(), tal como
scanf("%s", str);
entonces "UVWXYZ" se pondran en str.
Si aparece un espacio en la cadena de control (entre las comillas)
ejemplo:
scanf("%d %d", a,b );
Entonces scanf() empezar a leer pero no almacenar los espacios en
blanco.
Cap.1-36
Captulo 1: Principios fundamentales Programacin en C
Recordar que si aparece cualquier otro carcter en la cadena de control,
scanf() lee y descarta todos los caracteres que coincidan con l hasta que
encuentre el primer carcter que no sea coincidente. Por ejemplo, dada la
corriente de entrada 10t20, con:
scanf("%st%s", &x, &y);
Se pondr 10 en x y 20 en y. El carcter "t" se descarta porque es un carcter
de control.
Un ltimo punto: debido al uso de entrada con buffer de lnea tiende a
hacer de scanf() una funcin que dificulta la interactividad.
PALABRAS RESERVADAS DEL LENGUAJE
Como en los otros lenguajes de programacin, C consta de palabras clave
(reservadas) y reglas de sintaxis que se aplican a cada palabra clave. Una
palabra clave es esencialmente una orden y, a una gran extensin, las palabras
clave de un lenguaje definen lo que se puede hacer y cmo se har.
Tal como establece el estndar ANSI, existen 32 palabras reservadas (27
originales + 5 ANSI) que aparecern siempre en minscula, y se muestran a
continuacin:
PALABRAS RESERVADAS ORIGINALES
auto extern short
break float sizeof
case for static
char goto struct
continue if switch
default int typedef
do long union
double register unsigned
else return while
PALABRAS RESERVADAS INTRODUCIDAS POR ANSI
const signed volatile
enum void
PALABRAS RESERVADAS DE TURBO C
asm _cs _ds
_es ss cdel
far huge interrupt
near pascal
La segunda serie de palabras reservadas la introdujo el comit de
normalizacin ANSI.
Cap.1-37
Captulo 1: Principios fundamentales Programacin en C
Muchos compiladores han aadido varias palabras clave adicionales que
se utilizan para aprovechar mejor la organizacin de memoria de la familia de
procesadores 8086 y que dan soporte a la programacin con mltiples lenguajes
y a las interrupciones.
Cap.1-38
Captulo 1: Principios fundamentales Programacin en C
EJERCICIOS PROPUESTOS, CAPTULO 1
1) Suponiendo que todas las variables son de tipo int, indicar el
valor de cada una de las siguientes expresiones:
A) x = (5 + 2) * 4;
B) x = (3 + 2) / 4 * 5;
C) x = y = (2 + 4) * 6;
D) x = (int)2.4 * 4.5;
E) x = (3 + 3) * 10.5;
2) Dan el mismo resultado estas expresiones?
x = y / 3 - 34 * temp -127
x = (y / 3) - ( (34 * temp) -127 )
3) Evaluar las siguientes expresiones:
A) 2 > 3
B) a < b
C) 2 == 3
D) 3 == 3
4) Reescribir la siguiente instruccin utilizando el operador
incremental y usando el operador de asignacin aritmtica.
x = x + 1;
5) Indicar cul de las siguientes proposiciones son ciertas y cules
son falsas:
A) 10 > 3
B) B > D
C) 10 > 4 || 7 > 9
D) !(10 > 3)
E) !(10 == 9)
6) Construir una expresin para indicar las siguientes condiciones:
A) num es mayor que 3 y menor que 9.
B) ch no es una b ni una c.
C) num est entre 3 y 9 ambos inclusive. Pero no es 6.
D) num no est entre 3 y 9.
7) Cul es el valor numrico de las siguientes expresiones:
A) 7 > 2
B) 1 + 3 > 2 && 4 < 1
C) x > y || y > x || X == y
D) 5 + (7 > 4)
8) Dadas estas variables:
char ch;
short i;
unsigned long ul;
float;
Cul es el tipo que incluye a las dems en esta expresin?
f / ch -(i * ul);
Cul es el tipo de la subexpresin i*ul de arriba?
Cap.1-39
Captulo 1: Principios fundamentales Programacin en C
9) Qu muestra este programa?
#include <stdio.h>
main()
{
float f;
f = 10/3;
printf("%f", f);
}
10) Qu imprimir este programa?
#define NUM 4
main()
{
char car1, car2;
float num;
car1 = S;
car2 = O;
num = NUM ;
printf("%c%c%c-%1d-%2.1f\n", car1, car2, car1, NUM,num);
}
11) Qu imprimir el siguiente programa?
#include<stdio.h>
#define EJEMPLO "%s esto es un ejemplo\n"
main()
{
int x = 0;
printf (EJEMPLO,EJEMPLO);
printf ("%d\n", x++);
printf ("%d\n", ++x);
printf ("%d\n", x--);
printf ("%d\n", x);
}
12) Introducir, compilar y ejecutar el primer programa de la
teora.
13) Escribir un programa que declare una variable entera llamada
nmero y se le asigne el valor del ao actual. Mostrar el valor en pantalla
del modo siguiente:
El ao actual es: ____
14) Realizar un programa que lea una cadena que contiene 5 espacios
("Esta cadena si la toma scanf()") utilizando la funcin scanf(), por medio
del conjunto de inspeccin.
15) Escribir un programa que sume y reste 2 nmeros (punto flotante).
Los operandos deben ser ledos por teclado.
16) Calcular la longitud de una circunferencia. Pidindose el radio
y dando el resultado en pantalla.
17) Calcular el nmero de segundos que tiene un ao y presentarlo en
pantalla.
Cap.1-40
Captulo 1: Principios fundamentales Programacin en C
18) Son correctos estos comentarios?
/ ** /
/* printf("Esto es un comentario?"); */
19) Empleando funciones creadas por el usuario, realizar un programa
que calcule el cuadrado y el cubo de un nmero dado por teclado.
20) Dada una cantidad en formato decimal, presentar en pantalla su
equivalente hexadecimal y octal.
Cap.1-41
CAPTULO 2
Sentencias de Control en C
Captulo 2: Sentencias de control en C Programacin en C
2.1 INTRODUCCIN
Un programa es un conjunto de mandatos que puede someterse como unidad,
a un ordenador y utilizarse para dirigir su comportamiento. Cada uno de los
mandatos recibe el nombre de Sentencia o instruccin.
El conjunto de sentencias de programa son la esencia de cualquier
lenguaje. Las sentencias en C son ricas y poderosas y ayudan a explicar la
popularidad del lenguaje.
A continuacin se realiza una clasificacin de los tipos de sentencias
recogidas en C:
SECUENCIAL
Simple
Compuesta
CONDICIONAL
Simple
Doble
Mltiple
(ruptura)
(continuacin)
(por defecto)
REPETITIVA
Mientras
Hacer mientras
Para (desde Vini. hasta Vfin.) hacer
INCONDICIONAL
2.2 SENTENCIAS DE TIPO SECUENCIAL
Las sentencias se ejecutan en orden natural, una detrs de otra, segn
estn escritas en el cdigo fuente del programa; sin embargo, solo los
programas ms sencillos utilizan nicamente ejecucin secuencial. C ofrece
sentencias de control (ver apartados siguientes) que sirven para cambiar el
orden secuencial de ejecucin de las instrucciones.
Las secuencias de tipo secuencial se divide en:
- Sentencia simple
- Sentencia compuesta
Cap.2-2
Captulo 2: Sentencias de control en C Programacin en C
SENTENCIA SIMPLE
Las sentencias simples, tambin denominadas en ocasiones instrucciones
primitivas, son aquellas que el procesador ejecuta de forma inmediata.
En C se denomina sentencia a una expresin seguida de ";".
El formato general es:
<expresin> ;
Como ejemplo se presentan 3 sentencias; una de asignacin, otra de
entrada de datos y la ltima de salida de datos:
x = 12 + (10 * valor) / 5 ;
scanf("%d", &valor) ;
printf("Su valor es: %d", variable) ;
SENTENCIA COMPUESTA
Una sentencia compuesta es un grupo de sentencias individuales, cada una
separada de la siguiente por ";", y que son tratadas en su conjunto como una
sola sentencia.
Las sentencias compuestas estn encerradas entre llaves ("{", "}").
El formato general es:
{
sentencia_1 ;
sentencia_2 ;
.....
sentencia_n ;
}
Un ejemplo de sentencia compuesta podra consistir en agrupar las
sentencias del ejercicio anterior para que sean consideradas como una unidad:
{
x = 12 + (10 * valor) / 5 ;
scanf("%d", &valor) ;
printf("Su valor es: %d", variable) ;
}
2.3 SENTENCIAS CONDICIONALES
Las sentencias condiciones son aquellas que deciden la ejecucin de una
o varias instrucciones en funcin de una condicin dada. Existen tres tipos
de sentencias condicionales.
Cap.2-3
Captulo 2: Sentencias de control en C Programacin en C
- Condicional simple
- Condicional doble (y operador "?")
- Condicional mltiple
SENTENCIA CONDICIONAL SIMPLE
El formato general es:
if(expresin) sentencia ;
En esta instruccin, la condicin es una expresin BOOLEANA. La
sentencia (que puede ser simple o compuesta) se ejecuta solamente SI la
evaluacin de la condicin produce el resultado "CIERTO"; si es "FALSA"
(expresin = a "0") no se ejecuta la sentencia. Ejemplos:
Con sentencia simple:
if(n > 10)
printf("%d es mayor que 10 \n", n) ;
Cap.2-4
Captulo 2: Sentencias de control en C Programacin en C
Con sentencia compuesta:
if(opcion == 1)
{
printf("opcion = 1\n") ;
printf("Introducir variable\n") ;
scanf("%d", &variable) ;
}
Recordar que en C "verdadero" es cualquier valor que no sea "0" y falso
es "0". Por eso, es perfectamente vlido tener unas sentencias if como las
mostradas:
if(contador + 1) printf("no es cero") ;
if(contador) printf("no es cero) ;
if(i = j)... equivale a if((i = j) != 0)...
En la ltima sentencia el resultado es:
1 Se realiza la asignacin i = j; (el valor de j se transfiere a i)
2 Se comprueba si i es distinto de "0"
SENTENCIA CONDICIONAL DOBLE
El formato general es:
if(expresin)
sentencia_1 ;
else
sentencia_2 ;
Si la evaluacin de la condicin produce el resultado "CIERTO" se
ejecutar la sentencia_1; en caso contrario se lleva a cabo la sentencia_2.
Ejemplos:
if(n % 2 == 0)
printf("%d es par -even-\n",n) ;
else
printf("%d es impar -odd-\n",n) ;
Cap.2-5
Captulo 2: Sentencias de control en C Programacin en C
/* Uso de la funcin tolower() */
#include "stdio.h"
main()
{
int a, c;
printf("Escribe una tecla mayscula: ");
a = getchar();
c = tolower(a);
if(a >= A && a <= Z)
printf(\n %c en minsculas es %c.", a, c) ;
else
printf("\n No era maysculas !") ;
}
Uno de los aspectos ms confusos de las sentencias if en el lenguaje de
programacin es el if anidado. Un if anidado es una sentencia if que es el
objeto de otro if o else.
La razn por la que los if anidados son tan problemticos es que se
puede tener problemas para decidir qu else se asocia con qu if. Considerar
este ejemplo:
if(x)
if(y)
printf("1") ;
else
printf("2") ;
A qu if se refiere el else?, afortunadamente C proporciona una regla
sencilla para resolver esta pregunta. En C, el else est enlazado con el if
ms cercano que no tenga ya una sentencia else asociada. El if y el else deben
estar dentro del mismo bloque de cdigo. En este caso, el else asociado con
la sentencia if(y). Para asociar el else con la if(x), se debe usar llaves
para eludir la asociacin normal, como se muestra aqu:
if(x)
{
if(y)
printf("1") ;
}
else
printf("2") ;
Cap.2-6
Captulo 2: Sentencias de control en C Programacin en C
EL ESCALONADOR IF-ELSE-IF
Una construccin corriente es el escalonador if-else-if. Su formato
general es:
if(expresin_1)
sentencia_1
else if(expresin_2)
sentencia_2
else if(expresin_3)
sentencia_3
....
else if(expresin_n)
sentencia_n
else
sentencia_n+1
Las expresiones se evalan en orden; si alguna es cierta, se ejecuta la
sentencia asociada con ella y se acaba la cadena. Si todas son falsas se
ejecuta la sentencia asociada en el else final (accin por defecto).
A veces, existe una accin por defecto explcita (fuera de la sentencia
if - else - if), en este caso se puede omitir el ltimo else sentencia_n+1.
En el siguiente ejemplo del escalonador if-else-if, tan pronto como
aparece una sentencia if, se sobrepasan (puentean) el resto de las sentencias.
/* conversin de nmeros:
decimal -> hexadecimal; hexadecimal -> decimal
decimal -> octal; octal -> decimal */
#include <stdio.h>
main()
{
int opcion, valor ;
printf("Conversin :\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 opcin: ") ;
scanf("%d", &valor) ;
if(opcion==1)
{
printf("Intro. valor decimal: ") ;
scanf("%d", &valor) ;
printf("%d en hexa. es: %x", valor, valor) ;
}
else if(opcion==2)
{
printf("Intro. valor hexadecimal: ") ;
scanf("%x", &valor) ;
printf("%x en decimal es: %d", valor, valor) ;
}
else if(opcion==3)
{
printf("Intro. valor decimal: ") ;
scanf("%d", &valor) ;
Cap.2-7
Captulo 2: Sentencias de control en C Programacin en C
printf("%d en octal es: %o", valor, valor) ;
}
else if(opcion==4)
{
printf("Intro. valor octal: ") ;
scanf("%o", &valor) ;
printf("%o en decimal es: %d", valor, valor) ;
}
}
EL OPERADOR "?"
Se utiliza para decidir entre dos valores a asignar, dependiendo de la
evaluacin de una expresin lgica (binaria).
Se puede usar el operador "?" para reemplazar las sentencias if/else.
De formato general:
if(condicin)
expresin
else
expresin
La restriccin clave del operador "?" es que el objeto del if y del else
debe ser una expresin sencilla, no otra sentencia de C.
"?" se llama operador "ternario" porque requiere de tres operandos. Su
sintaxis es:
variable = condicin ? expresin_1 : expresin_2
Aqu, condicin es una expresin que se evala a verdadero o falso (Ej.
x > 9). Si es verdadero, se le asigna a variable el valor de expresin_1. Si
es falso, se le asigna a variable el valor de expresin_2. La razn del
operador "?" es que un compilador de C que use este operador puede producir
cdigo ms eficiente que si usa la sentencia equivalente if/else. Unos
ejemplos del operador ternario seran:
z = (a > b) ? a : b ;
printf("Hay %d elemento%s \n", n, n==1 ? "" : "s") ;
/* convierte un nmero introducido en 1 si es positivo y -1
si es negativo */
include "stdio.h"
main()
{
int i ;
printf("Introduzca nmero: ");
scanf("%d", &i);
i = i > 0 ? 1 : -1
printf("%d", i);
}
Cap.2-8
Captulo 2: Sentencias de control en C Programacin en C
SENTENCIA CONDICIONAL MLTIPLE -SWITCH-
Aunque el escalonador if-else-if puede realizar comprobaciones
mltiples, no es elegante. El cdigo puede ser difcil de seguir y puede
confundir incluso al programador pasado el tiempo. C tiene incorporada una
sentencia de bifurcacin mltiple llamada switch. En switch, el ordenador
comprueba una variable sucesivamente frente a una lista de constantes enteras
o de carcter. Despus de encontrar una coincidencia, la computadora ejecuta
la sentencia o bloque de sentencias que se asocia con la constante. Su
sintaxis es:
switch(expresin)
{
case constante_1:
secuencia de sentencias
break;
case constante_2:
secuencia de sentencias
break;
case constante_3:
secuencia de sentencias
break;
....
default:
secuencia de sentencias
break;
}
La sentencia switch se diferencia de if en que switch solamente puede
verificar la igualdad, mientras la expresin condicional if puede ser de
cualquier tipo. Adems, switch slo funcionar con tipos int o char. No se
puede, por ejemplo, utilizar nmero en punto flotante.
Las secuencias de sentencias asociada con cada case NO son bloques, es
decir, no estn encerrados entre llaves (la sentencia switch entera define un
bloque).
El ordenador ejecuta la secuencia default sino coincide ninguna
alternativa. El default es opcional; si no est presente, no se hace nada. Si
encuentra una coincidencia, la computadora ejecuta las sentencias asociadas
con el case hasta encontrar un break o el final de la sentencia switch. Por
ejemplo:
Cap.2-9
Captulo 2: Sentencias de control en C Programacin en C
#include <stdio.h>
main()
{
int i;
printf("Escriba un nmero: ") ;
scanf("%d", &i) ;
switch(i)
{
case 0:
printf("cero\n") ;
break ;
case 1:
case 2:
printf("un 1 o un 2\n") ;
break ;
case 4:
break ; /* si es 4 no escribe nada */
case 5:
case 6:
printf("un 5 o un 6\n") ;
break;
default:
printf("otros casos\n");
break; /* opcional */
}
}
El estndar ANSI establece que se permiten por lo menos 257 sentencias
case. En la prctica, se debera limitar la cantidad de sentencias case a un
nmero mucho menor por razones de eficacia. Adems no puede haber dos
constantes case con valores idnticos en el mismo switch.
Es posible tener un switch como parte de una secuencia de sentencias de
otro switch ms externo. Esto se llama switch anidados. Si las constantes case
del switch ms externo y ms interno contienen valores comunes, no surgirn
conflictos. Por ejemplo, el siguiente fragmento de cdigo es perfectamente
aceptable:
Cap.2-10
Captulo 2: Sentencias de control en C Programacin en C
switch(a)
{
case 1:
switch(b)
{
case 1:
printf("b es verdadero") ;
break ;
case f:
printf("b es falso") ;
break ;
}
case 2:
....
}
Un compilador del estndar ANSI permitir al menos 15 niveles de
anidamiento para sentencias switch.
Sentencia BREAK, funcionamiento en un SWITCH
Tcnicamente, las sentencias break son opcionales dentro de la sentencia
switch. Se utilizan para terminar la secuencia de sentencias que est asociada
con cada constante. Si se omite el break, la ejecucin continuar en las
sentencias del siguiente case hasta que el ordenador encuentre un break o el
final del switch. Pensar en los case como en etiquetas
1
. La ejecucin
comienza en la etiqueta que coincide con la variable de control y continuar
hasta que el ordenador encuentre la sentencia break o la marca de final del
switch.
2.4 SENTENCIAS REPETITIVAS (BUCLES)
Los bucles permiten repetir un conjunto de instrucciones hasta que se
cumple cierta condicin. C soporta el mismo tipo de bucles que otros lenguajes
estructurados modernos. Los bujes de C son:
- while (mientras)
- do - while (hacer - mientras)
- for (para)
BUCLE WHILE (Mientras)
Su formato general es:
while(condicin)
sentencia;
1
Una etiqueta es un nombre de identificador vlido seguido de dos puntos, sirve para sealar al
compilador un punto del programa.
Cap.2-11
Captulo 2: Sentencias de control en C Programacin en C
Donde sentencia, puede ser una sentencia vaca, sentencia nica o un
bloque de sentencias que se repetirn. La condicin puede ser cualquier
condicin vlida. El bucle itera MIENTRAS la condicin sea VERDAD. Cuando
llega a falsa, el control del programa pasa a la lnea que sigue al bucle.
Este ejemplo muestra una rutina de entrada desde teclado que simplemente
se repite hasta que se pulsa A:
espera_de_carac()
{
char ch;
ch = \0 ; /* inicializa ch */
while(ch != A)
ch = getch() ;
}
Primero, la rutina inicializa ch a nulo. Desde el momento que ch es una
variable local, su valor es desconocido cuando el ordenador ejecuta
espera_de_carac(). El bucle while entonces comienza comprobando para ver si
ch no es igual a A. Debido a que la rutina inicializa ch a nulo antes de
manejarla, la prueba es VERDAD y el bucle comienza. Cada vez que se pulsa una
tecla, el programa intenta comprobar de nuevo. Despus de pulsar A, la
condicin llega a ser falsa cuando ch es igual a A.
El bucle while comprueba la condicin en lo alto del bucle, lo que
significa que el cdigo del mismo no se ejecuta siempre. En el ejemplo dado,
esta es la razn que tiene ch para ser inicializada y evitar que contenga
accidentalmente A. Debido a que while realiza la prueba en lo alto, el while
es bueno para situaciones donde no se quiera ejecutar bucle.
Cap.2-12
Captulo 2: Sentencias de control en C Programacin en C
Otro ejemplo de while.
/* traduce los caracteres a un formato codificado
aadiendo 1 a cada letra */
#include <stdio.h>
#include <conio.h>
main()
{
char ch ;
printf("Introduzca su mensaje.\n") ;
ch = getche() ;
while (ch != \r)
{
printf("%c", ch+1) ;
ch = getch() ;
}
}
BUCLE DO-WHILE (Hacer - mientras)
Al contrario del bucle while que comprueba la condicin en lo alto del
bucle, el bucle do - while la examina en la parte baja del mismo. Esta
caracterstica provoca que un bucle do - while siempre se ejecuta AL MENOS una
vez. La forma general del bucle do - while es:
do
{
sentencia ;
} while(condicin) ;
Aunque no son necesarias las llaves cuando slo est presente una
sentencia, se usan normalmente por legibilidad y para evitar confusin al
programador (no en el compilador) con el while.
Este programa usa un do - while para leer nmeros desde el teclado hasta
que uno de ellos es menor que 100:
#include <stdio.h>
main()
{
int num ;
do
{
scanf("%d", &num) ;
} while(num > 100) ;
}
Quiz el uso ms comn para el do - while es una rutina de seleccin en
un men. Debido a que normalmente se quiere una rutina de seleccin de un men
para ejecutarse al menos una sola vez, el bucle do - while es una eleccin
obvia. Al comprobar una respuesta vlida al final del bucle, se puede avisar
Cap.2-13
Captulo 2: Sentencias de control en C Programacin en C
al usuario una y otra vez hasta que se introduzca una respuesta vlida. El
siguiente fragmento demuestra cmo aadir un bucle do - while al men del
programa de conversin de bases de nmeros:
/* control de una entrada correcta */
do
{
printf("Conversin :\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 opcin: ") ;
scanf("%d", &opcion) ;
} while(opcion < 1 || opcion > 4) ;
BUCLE FOR (Para)
La forma general de la sentencia for es:
for(inicializacin ; condicin ; incremento)
sentencia ;
En su forma ms simple, la inicializacin es una sentencia de asignacin
que el compilador usa para establecer la VARIABLE DE CONTROL de bucle. La
condicin es una expresin que comprueba la variable del bucle cada vez para
determinar cundo salir del bucle. El incremento define la manera en que
cambia la variable de control de bucle cada vez que la computadora repite el
mismo. Se deben separar estas tres grandes secciones usando punto y coma ";".
El bucle for continuar la ejecucin MIENTRAS la condicin sea VERDAD.
Una vez que es falsa la ejecucin del programa, continuar en la sentencia que
sigue al for.
Sentencia puede ser una sentencia nica o un bloque de cdigo (grupo de
sentencias) delimitado por llaves.
La instruccin for:
for(expresin_1 ; expresin_2 ; expresin_3)
sentencia;
Es equivalente a
expresin_1 ;
while(expresin_2)
{
sentencia;
expresin_3 ;
}
El usar while o for es principalmente cuestin de preferencia personal.
El for se prefiere cuando existe una inicializacin simple e incrementos,
puesto que mantiene las expresiones de control del bucle juntas y visibles al
principio del mismo.
Como ejemplo, el siguiente programa imprime los nmero de 1 a 100 en la
pantalla:
Cap.2-14
Captulo 2: Sentencias de control en C Programacin en C
#include <stdio.h>
main()
{
int x ;
for(x = 1 ; x <= 100 ; x++)
printf("%d ", x) ;
}
Este programa inicializa x a 1. Como x es menor que 100, el programa
llama a printf(). Despus de volver de printf(), el programa incrementa x en
1 y comprueba para ver si es todava menor que 100. Este proceso se repite
hasta que x es mayor que 100, en ese momento termina el bucle. En este ejemplo
x es la variable de control del bucle, que se cambia y comprueba cada vez que
se repite el bucle.
C permite crear bucles positivos y negativos (incrementos y decrementos
respectivamente de la variable de control) y adems utilizar cualquier tipo
de asignacin que se quiera, por ejemplo incrementos o decrementos superiores
a la unidad. A continuacin se muestran dos ejemplos ilustrativos:
#include <stdio.h>
main()
{
int x ;
for(x = 100 ; x > 0 ; x--)
printf("%d ", x) ;
}
#include <stdio.h>
main()
{
int x ;
for(x = 0 ; <= 100 ; x = x + 5)
{
printf("%d", x) ;
printf("___") ;
}
}
Un punto importante del bucle for es que siempre realiza la prueba
condicional en lo alto del bucle (igual que el bucle while). Esto significa
que la computadora puede no ejecutar el cdigo del bucle si la condicin es
falsa. Por ejemplo:
x = 10 ;
for(y = 10 ; y != x ; y++)
printf("%d", y) ;
printf("%d", y) ;
Cap.2-15
Captulo 2: Sentencias de control en C Programacin en C
El bucle no se ejecutar ya que x e y son iguales cuando el ordenador
entra en el bucle. Debido a esto la expresin condicional a evaluar es falsa,
la computadora no ejecutar el cuerpo del bucle ni la porcin de incremento
del bucle. As y todava tendr el valor 10 asignado a l y la salida tendr
slo el nmero 10 impresa una sola vez.
C permite diversas variaciones que incrementan el poder, la flexibilidad
y aplicabilidad del bucle for para ciertas aplicaciones:
A) Uso de dos o ms variables de control de bucle
B) Comprobar la variable de control de bucle frente un valor objeto
C) Bucles infinitos
D) Bucles sin cuerpo
Un ejemplo del primer caso lo encontramos en el siguiente programa:
#include <stdio.h>
main()
{
int x, y ;
for(x = 0, y = 0 ; x + y < 100 ; x++, y++)
printf("%d ", x+y) ;
}
Este programa imprime los nmeros de 0 al 98 incrementndose en 2.
Obsrvese las comas que separan las secciones de inicializacin e incremento.
Recordar que una pareja de expresiones separadas por una coma se evala de
izquierda a derecha (las comas que separan a los argumentos de una funcin,
las variables en declaraciones, etc, no son operadores coma) y que significa
haz esto Y esto. En cada interacin, el ordenador incrementa x e y. Ambos
pueden ser los valores correctos para determinar el bucle.
Como ejemplo del segundo apartado se puede indicar la siguiente
sentencia:
for(i = 1 ; i < 100 && var_1 = N ; i++)
Uno de los usos ms interesantes es el apuntado en el apartado C, el
bucle infinito. Debido a que ninguna de la tres expresiones que forman el
bucle for se dan, se puede hacer un bucle sin fin teniendo la expresin
condicional vaca, como se muestra en este ejemplo:
for(;;)
printf("este bucle funcionar siempre.\n);
El apartado D hace referencia a los bucles sin cuerpo, es decir, que el
espacio para la sentencia est vaco. Se puede usar este efecto para mejorar
la eficacia de ciertos algoritmos, as como crear bucles de retardos. El
siguiente muestra la manera de crear un retardo de tiempo usando for:
for(t = 0 ; t < UN_VALOR ; t++) ;
Por ltimo decir que se puede crear otra variacin importante del bucle
for ya que, en realidad, cada una de estas tres secciones (inicializacin,
condicin e incremento) puede constar de una expresin vlida en C. Las
expresiones no necesitan tener nada que hacer. Por ejemplo:
Cap.2-16
Captulo 2: Sentencias de control en C Programacin en C
for(prompt() ; t = readnum() ; prompt()) ....
BUCLES ANIDADOS
Cuando el cuerpo de un bucle contiene otro, se dice que el segundo est
anidado en el primero. Cualquiera de los bucles de C puede estar anidado
dentro de cualquier otro bucle. El C estndar ANSI especifica que los bucles
pueden estar anidados al menos 15 niveles. Sin embargo, la mayora de los
compiladores permiten anidamientos de prcticamente cualquier nivel. Como un
simple ejemplo de for anidados, este fragmento imprime 10 veces en pantalla
los nmeros del 1 al 10:
for(i = 0 ; i < 11 ; i++)
{
for(j = 1 ; j < 11 ; j++)
printf("%d ", j) ;
printf("\n");
}
SENTENCIAS "BREAK" Y "CONTINUE" EN UN BUCLE
La sentencia break permite salir de un bucle desde cualquier punto de
su cuerpo, pasando por alto su expresin de finalizacin normal. Cuando la
sentencia break se encuentra dentro de un bucle, el bucle termina
inmediatamente y el control de programa contina en la sentencia que sigue al
bucle. A continuacin se muestran dos ejemplos:
#include <stdio.h>
main()
{
int i ;
for(i = 1 ; 1 < 100 ; i++)
{
printf("%d ", i) ;
if(i==10)
break; /* salir del bucle */
}
}
for(;;)
{
ch = getche() ; /* coge un carcter */
if(ch == A)
break ; /* sale del bucle cuando se pulsa A */
}
printf("pulsada la tecla A");
La sentencia break se puede utilizar con cualquiera de los tres bucles
de C.
Se pueden tener tantas sentencias break dentro de un bucle como se
deseen. Sin embargo, puesto que demasiados puntos de salida en un bucle
tienden a romper la estructura del cdigo, normalmente lo mejor es utilizar
break en casos especiales, y no como salida habitual del bucle.
La sentencia continue es algo as como el opuesto de la sentencia break.
Obliga a que se produzca la siguiente iteracin del bucle, saltando cualquier
cdigo entre ella y la condicin de prueba del bucle. Por ejemplo, este
programa nunca muestra ninguna salida:
Cap.2-17
Captulo 2: Sentencias de control en C Programacin en C
#include <stdio.h>
main()
{
int x ;
for(x = 0 ; x < 100 ; x++)
{
continue;
printf("%d ", x); /* esto no se ejecuta nunca */
}
}
Cada vez que se alcanza la sentencia continue, hace que se repita el
bucle, saltndose la sentencia printf().
En los bucles while y do - while, una sentencia continue har que el
control vaya directamente a la condicin de prueba y que contine despus del
proceso del bucle. En el caso de for, se lleva a cabo la parte de incremento
del bucle, se ejecuta la prueba condicional y el bucle contina.
2.5 SALTO INCONDICIONAL
Un salto incondicional es aquel que se produce forzosamente sin
necesitar ninguna condicin para llevarse a cabo. Los programas escritos en
lenguaje ensamblador utilizan con mucha frecuencia esta instruccin. Pero
dentro de la programacin estructura est prcticamente prohibido su uso. C
soporta una sentencia de salto no condicional llamada goto. Ya que C es una
sustitucin del cdigo ensamblador, la inclusin de goto es necesaria,
permitiendo utilizar rutinas muy rpidas. Sin embargo, la mayora de los
programadores no utilizan goto porque rompe la estructura del programa y, si
se utiliza con mucha frecuencia, puede hacer que el programa sea casi
imposible de entender. Adems, no hay ninguna rutina que requiera goto. Por
estas razones, no se utiliza fuera de esta seccin.
El goto requiere de una etiqueta para funcionar. An ms la etiqueta
debe estar en la misma funcin que usa el goto. Por ejemplo, se podra
escribir un bucle de 1 a 100 usando goto y una etiqueta como se ve aqu:
x = 1 ;
bucle_1: /* destino del salto */
x++ ;
if(x < 100)
goto bucle_1; /* salto incondicional */
El goto es un convenio que, si se usa con cuidado, puede ser beneficioso
en ciertas situaciones de programacin. Por ejemplo saltar una rutina con un
anidamiento muy grande cuando ocurre un error catastrfico:
Cap.2-18
Captulo 2: Sentencias de control en C Programacin en C
var_1 = 0 ;
for(...)
{
for(...)
{
for(...)
{
while(...)
{
if(...)
{
var_1 = 0 ;
goto fin:
....
}
}
}
}
}
fin:
printf("error en el programa\");
Se debera usar contadas veces goto - si no en absoluto -. Sin embargo,
si el cdigo fuera ms difcil de leer o la ejecucin de velocidad crtica,
entonces por encima de todo usar goto.
Cap.2-19
Captulo 2: Sentencias de control en C Programacin en C
EJERCICIOS PROPUESTOS, CAPTULO 2
1) Qu imprimir el siguiente programa?:
#include <stdio.h>
main()
{
int i = 0 ;
while (i < 4)
{
switch(i++)
{
case 0 : printf ("Cul") ;
case 1 : printf (" es el") ;
case 2 : printf (" resultado") ;
case 3 : printf (" del") ;
default: printf (" ejercicio") ;
}
printf("\n") ;
}
}
2) Qu imprimir el programa si modificamos el cuerpo del "case 2"
de la siguiente forma?:
case 2: printf(" resultado") ;
break ;
3) Qu imprimir el siguiente programa?:
#include <stdio.h>
#define MENSAJE "DOMINO EL LENGUAJE C?"
main()
{
int i = 0 ;
while(i < 5)
printf(%s\n", MENSAJE) ;
i++;
}
4) Qu imprimir el siguiente programa?:
#include <stdio.h>
#define DIBUJO "*"
main()
{
int i, j, k = 4 ;
for(i = 1; i <= 4 ; i++)
{
for(j = 1; j < = k ; j++)
printf(DIBUJO);
k-- ;
printf("\n") ;
}
}
Cap.2-20
Captulo 2: Sentencias de control en C Programacin en C
5) Crear un programa que indique si un n introducido es mltiplo de
3.
6) Utilizando una funcin que genere nmeros aleatoriamente (random()
o randomize() -stdlib.h-) disear un programa que solicite un n (0 - 100)
al usuario y que devuelva un mensaje de acierto si dicho n coincide con el
n aleatorio obtenido anteriormente.
7) Implementar un algoritmo que primero solicite el margen de valores
(v_max y v_min) para que posteriormente al introducir el usuario un n, el
programa indique si est o no dentro de los lmites marcados previamente.
Utilizar el operador ternario "?".
8) Realizar un programa que solicite un carcter y que devuelva el
tipo de carcter de que se trata:
- Numrico
- Alfabtico (minsculas)
- Alfabtico (maysculas)
- otro
9) Crear un programa que muestre por pantalla el siguiente mensaje:
---------- Men de diagnstico ----------
A: Test del sistema
B: Test de video
C: Test de discos duros
D: Test de teclado
Introduzca letra para seleccionar el test =>
Una vez indicada la opcin se devolver un mensaje que indique la opcin
seleccionada. Si la tecla pulsada no corresponde con ninguna de las cuatro
opciones debe aparecer el texto "No eligi ningn test".
Nota: la funcin toupper() -ctype.h- convierta a mayscula el carcter
alfabtico pulsado.
10) Disear un algoritmo que fuerza a permanecer en un bucle hasta que
el usuario pulse una tecla que corresponda a una letra minscula.
11) Crear un programa (utilizando el bucle WHILE) que permita
convertir un nmero en base decimal a cualquier otra base (entre 2 y 9).
Debe pedirse la base destino y el n a convertir.
12) Modificar el ejercicio nmero 9 para que repita la presentacin
del mensaje mientras no se pulse una de las opciones deseadas.
13) Utilizando bucles de tipo FOR crear un programa que genere una
ventana con los mrgenes asignados por el usuario.
Deben indicarse los siguientes datos: margen superior izquierdo de la
ventana y n de columnas y filas a partir de dicho punto.
Para el desarrollo de este programa pueden utilizarse las siguientes
funciones de la librera conio.h:
gotoxy() .- sita el cursor en un punto determinado (columna, fila)
wherex() .- toma el valor de la columna actual
wherey() .- toma el valor de la fila actual
textbackground() .- color del fondo
textcolor() .- color de lnea
cprintf() .- presenta por pantalla un texto con el color de salida
previamente fijado
Cap.2-21
Captulo 2: Sentencias de control en C Programacin en C
Los colores posibles son:
BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE
LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW y WHITE.
lowvideo()
normvideo()
highvideo()
BLINK .- parpadeo.
14) Realizar un programa que escriba la tabla de multiplicar del 1 al
9.
15) Crear un programa que realice la tabla de multiplicar de un n
introducido por teclado (solamente vlido para nmeros del 1 al 10).
16) Desarrollar un algoritmo que permita calcular el mximo comn
divisor de dos nmeros.
17) Disear un programa que lee nmeros y muestra su suma y la media,
hasta que se introduzca el 0.
18) Utilizando la sentencia CASE realiza un programa que simula una
calculadora simple.
19) Crear un programa que imprima por pantalla la serie de Fibonacci
(0 1 1 2 3 5 ...), hasta un lmite pedido al usuario.
20) Disear un programa que realice una tabla de cuadrados y cubos de
los n primeros nmeros.
Cap.2-22
CAPTULO 3
Tablas y cadenas
Captulo 3: Tablas y cadenas ProgramacinenC
3.1 INTRODUCCIN
Una tabla (matriz o array) es un conjunto de variables del mismo tipo,
situadas (secuencialmente) en posiciones de memoria contiguas, que se
referencian utilizando un nombre comn.
La direccin ms baja corresponde al primer elemento y la ms alta al
ltimo. Una tabla puede tener una o varias dimensiones. Para acceder a un
elemento especfico de la tabla se utilizan tantos ndices como dimensiones
tenga.
El tamao de la tabla es su nmero de elementos. Debe fijarse cuando se
declare la matriz (el tamao y el tipo determinan el espacio de memoria que
se destinar a la tabla).
No hay lmites, excepto el que determine fsicamente la memoria del
ordenador, para el nmero de dimensiones.
3.2 TABLAS DE UNA DIMENSIN
El formato general para la declaracin de un tabla unidimensional es:
tipo nombre_variable[tamao]
Aqu tipo declara el tipo -base- de la tabla. El tipo base determina el
tipo de datos que contendr cada elemento de la tabla. El tamao define
cuntos elementos guardar. Por ejemplo, lo siguiente declara una tabla de
enteros llamada grupo_a de 10 elementos:
int grupo_a[10] ;
En C, todas las tablas usan cero "0" como ndice del primer elemento.
Por tanto el ejemplo anterior declara un array de enteros de 10 elementos:
grupo_a[0] a grupo_[9].
Ilustr. 1 Declaracin de una matriz
unidimensional
Las tablas son muy comunes en programacin porque permiten tratar
fcilmente muchas variables relacionadas.
En el siguiente ejemplo, el uso de una tabla facilita el clculo de la
media de una lista de nmeros. Lee 10 nmeros enteros que introdujo el usuario
y a continuacin visualiza la media:
Cap.3-2
Captulo 3: Tablas y cadenas ProgramacinenC
/* realiza la media de 10 nmeros */
#include <stdio.h>
main()
{
int ejemplo[10] ; /* reserva diez elementos enteros */
int i, suma ;
for(1 = 0 ; 1 < 10 ; i++)
{
printf("introducir un n para la casilla %d", i) ;
scanf("%d", &ejemplo[i]) ;
}
suma = 0 ;
/* ahora realiza la media */
for(i = 0 ; i < 10 ; i++)
suma += ejemplo[i] ;
printf("\nLa media es %d \n", suma / 10) ;
}
Ilustr. 2 Indexacin de una tabla
El lenguaje C no realiza comprobacin del lmite definido en la tabla.
As no detiene la escritura fuera del final del array. Si se sobrepasa el
final durante una operacin de asignacin, entonces se asignarn valores a
otra variable o a un trozo del cdigo del programa.
Como programador, se es responsable de asegurar que todas las tablas
sean lo suficientemente grandes para guardar lo que pondr en ellos el
programa y de proporcionar las suficientes comprobaciones del lmite cuando
sea necesario.
Porqu C no realiza la comprobacin del lmite?. La respuesta es que
C fue diseado para reemplazar la codificacin en lenguaje ensamblador en
Cap.3-3
Captulo 3: Tablas y cadenas ProgramacinenC
muchas situaciones. Para hacerlo, C no incluye virtualmente ninguna
comprobacin de error porque ralentiza (con frecuencia dramticamente) la
ejecucin de un programa. En vez de eso, C espera que el programador sea lo
suficientemente responsable para prevenir el desbordamiento de las tablas.
3.3 CADENAS DE CARACTERES
El uso ms corriente de una tabla unidimensional es crear cadenas de
caracteres. En C, una cadena consta de un grupo de caracteres que termina con
el carcter \0 que especifica el carcter nulo. En la mayora de los
compiladores, incluido el Turbo C, es 0.
Por esta razn, se deben declarar las tablas de caracteres con un
carcter de ms que la cadena ms larga vaya a guardar.
Ilustr. 3 Interior de una tabla que contiene una cadena de
caracteres
Por ejemplo, si se quiere declarar la tabla texto para que guarde una
cadena de 10 caracteres, se escribir:
char texto[11] ;
Esta declaracin hace sitio para un -nulo- al final de la cadena.
Recordar que una constante de cadena es una lista de caracteres que se
encierran entre dobles comillas.
No se necesita aadir el -nulo- manualmente en el final de las
constantes de cadenas porque el compilador lo hace automticamente. As la
cadena "cadena" se visualizara en memoria como viene presentado en la
ilustracin anterior.
LECTURA DE CADENAS DESDE TECLADO
La mejor manera de introducir una cadena desde el teclado es usando la
funcin de biblioteca gets(). El formato general de gets() es:
gets(nombre_tabla) ;
Para leer una cadena, se puede llamar a gets() con el nombre de la
tabla, sin ningn ndice, como argumento. Despus de volver de gets(), la
tabla guardar la cadena que se introduce desde el teclado. La funcin gets()
guardar caracteres hasta que se introduzca un retorno de carro <Enter>. El
archivo de cabecera que gets() utiliza es stdio.h.
Por ejemplo, este programa simplemente repite la cadena que se teclea:
Cap.3-4
Captulo 3: Tablas y cadenas ProgramacinenC
/* Un sencillo programa de cadenas */
#include <stdio.h>
main()
{
char cadena[80] ;
printf("Introducir una cadena: ") ;
gets(cadena) ; /* lee una cadena desde el teclado */
printf("%s", cadena) ;
}
Tener en cuenta que gets() no realiza ninguna comprobacin de contorno
en la tabla con que se llama.
ESCRITURA DE CADENAS EN PANTALLA
Hasta aqu, para visualizar la cadena guardada en una tabla de
caracteres usando printf(), se ha utilizado el formato bsico:
printf("%s", nombre_tabla) ;
Sin embargo, recordar que el primer argumento de printf() es una cadena
y que printf() imprime todos los caracteres que no son rdenes de formato. Por
tanto, si slo se quiere imprimir una cadena, se puede usar este formato:
printf(nombre_tabla) ;
El ejemplo siguiente demuestra este formato, modificando el programa
anterior:
#include <stdio.>
main()
{
char cadena[80] ;
printf("Introduce una cadena: ") ;
gets(cadena) ;
printf(cadena) ;
}
Otra opcin para la presentacin en pantalla de una cadena es utilizar
la funcin puts(). El formato general de puts() es:
puts(nombre_cadena)
La funcin puts() muestra en pantalla la cadena de caracteres indicada.
Aade automticamente una secuencia de retorno de carro, salto de lnea. Si
se ejecuta correctamente, puts() devuelve un valor no negativo. Si se produce
un error, devuelve EOF.
La razn principal por la que se prefiera utilizar puts() en lugar de
printf() para mostrar una cadena es que puts() es una funcin ms pequea y
ms rpida. El archivo de cabecera que puts() utiliza es stdio.h.
Cap.3-5
Captulo 3: Tablas y cadenas ProgramacinenC
3.4 INICIALIZACIN DE UNA TABLA
C permite la inicializacin global de tablas. No se pueden inicializar
tablas locales. (Realmente, se pueden inicializar variables locales,
incluyendo tablas, si se declaran como static. Se aprender este proceso ms
adelante). El formato general de la inicializacin de una tabla es similar al
de otras variables, segn se muestra a continuacin:
tipo nombre_tabla[i]...[n] = { valor_1, valor_2, ...; valor_n } ;
El grupo de valores es una lista separada por comas de constantes que
son de tipo compatible con el tipo base de la tabla. Esta sentencia pondr la
primera constante en la primera posicin del array, la segunda en la segunda
posicin, y as sucesivamente. Observar que despus de "}" va un punto y coma.
El ejemplo siguiente inicializa una tabla de 10 elementos enteros con los
nmeros del 1 al 10:
int i[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } ;
Esta sentencia indica que i[0] tendr el valor 1 e i[9] el 10.
Las tablas de caracteres que guardan cadenas permiten una inicializacin
abreviada que tiene la forma:
char nombre_tabla[tamao] = "cadena" ;
Por ejemplo, este fragmento de cdigo inicializa texto a "hola":
char texto[5] = { h, o, l, a, \0 } ;
Puede dejarse al compilador la tarea de asignar automticamente la
longitud de una tabla utilizando tablas sin tamao. En una sentencia de
iniciacin de tablas, si no se especifica el tamao de las mismas, entonces
el compilador crear automticamente una tabla lo suficientemente grande para
guardar los valores iniciales presentados (obligatorios cuando no hay tamao
implcito). Por ejemplo:
char e2[] = "autorizacin denegada" ;
Adems de ser menos tedioso, el mtodo de inicializacin del array sin
tamao permite cambiar cualquier mensaje sin tener que preocuparse por errores
accidentales de contaje.
3.5 FUNCIONES DE CADENA DE LA BIBLIOTECA
C soporta un amplio rango de funcin de manipulacin de cadenas. Las ms
corriente son:
strcpy() /* copia una cadena en otra */
strcat() /* aade (concatena) una cadena a otra */
strcmp() /* compara dos cadenas */
strlen() /* longitud de una de una cadena */
Las funciones de cadena utilizan todas el mismo archivo cabecera:
string.h. Ahora se revisan.
strcpy()
Una llamada a strcpy() tiene el siguiente formato:
Cap.3-6
Captulo 3: Tablas y cadenas ProgramacinenC
strcpy(cadena_destino, cadena_origen) ;
Se usa strcpy() para copiar los contenidos de la cadena_origen a la
cadena_destino incluyendo \0. Recordar que la tabla que guarde la
cadena_origen debe ser lo suficientemente grande. El siguiente programa
representa un ejemplo de la funcin strcpy(). Copiar "Texto copiado" en la
cadena cadena.
#include <stdio.h>
#include <string.h>
main()
{
char cadena[80] ;
strcpy(cadena, "Texto copiado") ;
printf(cadena) ; /* igual que = printf("%s", cadena) ; */
}
strcat()
Una llamada a strcat() tiene el siguiente formato:
strcat(cadena_destino, cadena_origen) ;
La funcin strcat() aade cadena_origen al final de cadena_destino; la
cadena_origen no se altera. Ambas cadenas terminan en un nulo y el resultado
final termina en un nulo. El siguiente programa imprimir cadenacompleta en
la pantalla:
#include <stdio.h>
#include <string.h>
main()
{
char cadena_ini[20], cadena_fin[20] ;
strcpy(cadena_fin, "cadena") ;
strcpy(cadena_ini, "completa") ;
strcat(cadena_fin, cadena_ini) ;
printf(cadena_fin) ;
}
strcmp()
La funcin strcmp() tiene el siguiente formato:
strcmp(cadena_1, cadena_2) ;
La funcin strcmp() compara dos cadenas y devuelve "0" si son iguales.
Si cadena_1 es lexicogrficamente (en n de caracteres) mayor que cadena_2,
entonces la funcin devuelve un nmero positivo; si cadena_1 es menor que
cadena_2, la funcin devuelve un nmero negativo.
Se puede usar la funcin siguiente como una rutina de verificacin de
palabra de acceso:
Cap.3-7
Captulo 3: Tablas y cadenas ProgramacinenC
/* Devuelve verdad si acepta la contrasea, INTRO */
contrasea()
{
char cadena_contra[80] ;
printf("Introducir la contrasea : ") ;
gets(cadena_contra) ;
if ( strcmp(cadena_contra, "INTRO") ) /* diferentes */
{
printf("palabra de acceso invlida") ;
return 0 ;
}
return 1 ; /* cadenas comparadas iguales */
}
Recordar que strcmp() devuelve fALSO ("0") cuando COINCIDEN las cadenas.
Utilizar "!" para invertir la condicin (VERDADERO cuando sean iguales)
strlen()
El formato de la funcin strlen() es el siguiente:
strlen(cadena) ;
Donde cadena es una cadena de caracteres. La funcin strlen() devuelve
la longitud de la cadena que seala strlen(). La funcin strlen() no cuenta
el terminal nulo (que marca el final de la cadena).
El siguiente programa imprime el inverso de la cadena que se introduce
por teclado. Si se introduce comic provoca que el programa imprima cimoc.
Recordar que las cadenas de caracteres son simplemente tablas que contienen
en cada casilla un carcter; de forma que se puede uno referir a cada carcter
individualmente.
/* Imprime una cadena hacia atrs */
#include <stdio.h>
#include <string.h>
main()
{
char cadena[80] ;
int i ;
printf("Introduzca una cadena: ") ;
gets(cadena) ;
for(i = (strlen(cadena) - 1) ; i >= 0 ; i--)
printf("%c", cadena[i]) ;
}
Puede argumentarse que la ventaja de que las cadenas terminen todas en
nulo es simplificar las diversas operaciones de salida. Por ejemplo, mirar
cmo lo requiere este pequeo cdigo que pone en maysculas una cadena:
Cap.3-8
Captulo 3: Tablas y cadenas ProgramacinenC
/* Convierte una cadena a maysculas */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
main()
{
char cadena[80] ;
int i ;
strcpy (cadena, "esto es una prueba") ;
for( i = 0 ; cadena[i] ; i++)
cadena[i] = toupper(cadena[i]) ;
printf(cadena) ;
}
Este programa imprimir ESTO ES UNA PRUEBA. Para convertir cada carcter
en la cadena, el programa usa la funcin de biblioteca toupper(), que devuelve
el equivalente en maysculas al argumento de carcter indicado. Utilizar el
archivo cabecera ctype.h. Observar que la condicin de prueba del bucle for
es simplemente la tabla indexada por i. La razn de que este programa funcione
es que es verdad cualquier valor distinto de cero. Por tanto, el bucle
funciona hasta que encuentra el terminal nulo, que es cero. Puesto que el
terminador nulo marca el final de la cadena, el bucle se para precisamente
donde tiene que pararse.
Como ejemplo final, el programa siguiente ilustra el uso de tres de las
cuatro funciones de cadena estudiadas:
#include <stdio.h>
#include <string.h>
main()
{
char c_1[80], c_2[80] ;
printf("introduzca dos cadenas: ") ;
gets(c_1) ;
gets(c_2) ;
printf("longitud: %d y %d\n", strlen(c_1), strlen(c_2)) ;
if( !strcmp(c_1 , c_2) )
printf("las cadenas son iguales a\n") ;
strcat(c_1 , c_2) ;
printf(c_1) ;
}
Si se ejecuta este programa y se introducen las cadenas hola y hola, la
salida ser:
Cap.3-9
Captulo 3: Tablas y cadenas ProgramacinenC
longitud: 5 y 5
las cadenas son iguales a
holahola
3.6 TABLAS MULTIDIMENSIONES
C permite trabajar con tablas de ms de una dimensin. El formato
general de una declaracin de tabla multidimensional es:
tipo nombre_tabla[tamao_1][tamao_2]...[tamao_n] ;
No se utilizan tablas de tres o ms dimensiones frecuentemente por la
cantidad de memoria que se requiere. El almacenamiento de las casillas
globales de una tabla se asigna permanentemente durante la ejecucin de su
programa.
Ilustr. 4 Organizacin de las tablas en funcin del nmero de dimensiones
Por ejemplo, una tabla de cuatro dimensiones de caracteres con
dimensiones 10, 6, 9, 4 requerira:
10 * 6 * 9 * 4 casillas
Si el tipo definido es char (1 casilla = 1 byte) ocupara 2.160 bytes.
Si el tipo es entero (int), como requiere 2 bytes, necesitara 4.320 bytes.
Si la tabla fuera double (8 bytes de largo), entonces requerira 34.560 bytes.
El almacenamiento requerido se incrementa exponencialmente con el nmero de
dimensiones. Un programa con tablas de ms de tres o cuatro dimensiones
puede encontrarse fuera de la memoria !.
Cap.3-10
Captulo 3: Tablas y cadenas ProgramacinenC
TABLAS DE 2 DIMENSIONES
La forma ms sencilla de tabla multidimensional es la bidimensional.
Para declarar una tabla (tabla_2d) de enteros de 2 dimensiones de tamao 10,
20 se escribira:
int tabla_2d[10][20] ;
Debe ponerse atencin a la declaracin: al contrario de la mayora de
los lenguajes de alto nivel, que usan comas para separar las dimensiones de
la tabla, C pone a cada dimensin sus propios parntesis cuadrados.
Del mismo modo, para acceder a la celdilla 3, 5 de tabla_2d, se usara
tabla_2d[3][5]. Este programa carga una tabla de 2 dimensiones con los nmeros
1 al 12:
#include <stdio.h>
main()
{
int num[3][4] ;
int y, x ;
for(y = 0 ; y < 3 ; y++)
for(x = 0 ; x < 4 ; x++)
num[y][x] = (y * t) + x + 1 ;
}
En este ejemplo, num[0][0], tendr el valor 1, num[0][1] tendr el valor
2, num[0][2] tendr el valor 3, y as sucesivamente. El valor de num[2][3]
ser 12.
El lenguaje C guarda las tablas bidimensionales en una matriz de filas
y columnas, donde el primer ndice indica la fila y el segundo la columna.
Esta estructura significa que el ndice ms a la derecha cambia ms rpido que
el de la izquierda cuando se accede a una tabla de elementos en el orden que
C realmente almacena en memoria. La ilustracin nmero 5 contiene una
representacin grfica de una tabla bidimensional en memoria. Se puede pensar
en el primer ndice como el "puntero" a la fila correcta.
Debe recordarse que el almacenamiento de los elementos de una tabla se
asigna en tiempo de compilacin. Esto significa que se necesita la memoria que
contiene una tabla mientras se est ejecutando el programa. Es decir, es
posible que tras la compilacin no exista ningn error (puesto que la sintaxis
es correcta) pero suceda que cuando el programa tome la memoria del sistema
, para guardar datos en las celdillas de la tabla el ordenador, nos d un
mensaje de error (o bien se nos quede colgado) por no disponer de la cantidad
suficiente de memoria.
Cap.3-11
Captulo 3: Tablas y cadenas ProgramacinenC
Ilustr. 5 Tabla bidimensional en memoria
Se inicializan tablas multidimensionales de la misma manera que las de
una dimensin. Por ejemplo, lo siguiente inicializa cuadrados con los nmeros
del 1 al 10 y sus cuadrados:
int cuadrados[10][2] =
{
1, 1,
2, 4,
3, 9,
4, 16,
5, 25,
6, 36,
7, 49,
8, 64,
9, 81,
10, 100,
} ;
El lenguaje C no restringe las inicializaciones de tablas sin tamao
slo a las matrices unidimensionales. Para tablas de ms de una dimensin, se
debe especificar todas las dimensiones excepto las de ms a la izquierda para
permitir indexar la tabla ms apropiadamente. En este sentido, se puede
construir tablas de longitud variable mientras el compilador asigna
automticamente el almacenamiento suficiente. Por ejemplo, sta es la
declaracin de cuadrados de una tabla sin tamao:
Cap.3-12
Captulo 3: Tablas y cadenas ProgramacinenC
int cuadrados[][2] =
{
1, 1,
2, 4,
3, 9,
4, 16,
5, 25,
6, 36,
7, 49,
8, 64,
9, 81,
10, 100,
} ;
TABLAS DE CADENAS
En programacin es corriente usar una tabla de cadenas. Por ejemplo, el
procesador de entrada para una base de datos puede verificar las rdenes de
usuario frente a un tabla de cadenas vlidas. Para crear una tabla de cadenas,
se usa una tabla de caracteres de dos dimensiones, en la que el tamao ndice
izquierdo determina el nmero de cadenas y el tamao derecho especifica la
longitud mxima de cada cadena. Por ejemplo, esto declara una tabla de 30
cadenas con cada cadena como mximo de 80 caracteres:
char tabla_cad[30][80] ;
El acceso a una cadena individual es muy fcil: simplemente se
especifica slo el ndice izquierdo. Por ejemplo, esta sentencia llama a
gets() con la tercerca cadena en tabla_cad:
gets(tabla_cad[2]) ;
Esta sentencia es funcionalmente equivalente a:
gets(&tabla_cad[2][0]) ;
Sin embargo, el primer formato es mucho ms comn en el cdigo C
profesional. (Se requiere "&" en el segundo formato, por razones que se
aclararn en el siguiente captulo).
Para entender la forma en que trabaja la tabla de cadenas, estudiar el
siguiente programa corto que acepta las lneas del texto introducido en el
teclado y vuelven a visualizarse cuando se haya introducido la lnea en
blanco.
Cap.3-13
Captulo 3: Tablas y cadenas ProgramacinenC
/* Introduce y visualiza lneas */
#include <stdio.h>
main()
{
int t, i ;
char texto[100][80] ;
for(t = 0 ; t < 100 ; t++)
{
printf("%d: ", t) ;
gets(texto[t]) ;
if(!(texto[t])) /* t contendr el n con blanco */
break ;
}
for(i = 0 ; i < t ; i++) /* revisualiza las cadenas*/
printf("%s\n", texto[i]) ;
}
Este programa introduce lneas de texto hasta que el usuario introduzca
una lnea en blanco. El programa entonces revisualiza cada lnea.
Cap.3-14
Captulo 3: Tablas y cadenas ProgramacinenC
EJERCICIOS PROPUESTOS, CAPTULO 3
1) Realizar un programa que pide una cadena por teclado (el nombre
y apellidos) y aada la cadena "Sr. D." mostrando el resultado por pantalla.
2) Hacer un programa que pide el nombre y apellidos y lo muestre al
revs, repitiendo la operacin hasta que se teclea "N".
3) Qu est mal en este fragmento ?:
#include <stdio.h>
main()
{
int i, contador[10] ;
for(i = 0 ; i < 100 ; i++) ;
{
printf("Introduzca un nmero: ") ;
scanf("%d", &contador[i]) ;
}
} */
/* No implementar este programa */
4) Escribir un programa que lea diez nmeros introducidos por el
usuario y que informe sobre si alguno coincide.
5) Realizar un programa que permita introducir hasta 100 nmeros y
despus los ordene. Para ello utilizar el mtodo de ordenacin de la burbuja.
El algoritmo de ordenacin de la burbuja es (de menor a mayor):
- se compara cada miembro de la tabla con el inmediatamente
posterior, si el siguiente es menor los nmeros se invierten de posicin.
- debe repetirse la comparacin de todos los nmeros hasta que se
recorra completamente la tabla sin ningn cambio.
6) Qu est mal en este programa ?
#include <string.h>
#include <stdio.h>
main()
{
char cadena[5] ;
strcpy(cadena, "esto es una prueba") ;
printf(cadena) ;
}
7) Escribir un programa que introduzca cadenas repetidamente. Cada
vez que se introduzca una cadena, tiene que concatenarse con una segunda
cadena llamada bigstr. Aadir caracteres de nueva lnea al final de cada
cadena y mostrar bigstr (que contendr un registro de todas las cadenas
introducidas). Tambin tendr que detenerse si la siguiente concatenacin va
a sobrepasar el tamao de bigstr.
Cap.3-15
Captulo 3: Tablas y cadenas ProgramacinenC
8) Crear un programa que guarde en tabla de 4 * 5 casillas los
productos de los ndices y despus muestre la tabla en formato de filas y
columnas.
9) Disear un programa que defina una tabla de tres dimensiones de
3 * 3 * 3, y cargue en l los nmeros del 1 al 127.
10) Modificar el programa anterior para que muestre la suma de todos
sus elementos.
11) Son correctos estos fragmentos de programas ?.
A)
int balance[] = 10, 0, 122, 23, 100, 0 ;
B)
#include <stdio.h>
#include <string.h>
main()
{
char nombre[] = "Andreu" ;
strcpy(nombre, "Andreu Martn") ;
}
12) Crear un programa que inicialice una tabla de 10 x 3 de modo que
el primer elemento de cada fila contenga un nmero, el segundo elemento su
cuadrado y el tercer elemento contenga un cubo. Debe empezarse con uno y
terminar con 10. Por ejemplo, las primeras filas seran as:
1, 1, 1,
2, 4, 8,
3, 9, 27,
...
Despus, que pida el usuario un cubo, debe buscarse ese valor en la
tabla, e informa de cul es la raz del cubo y el cul es el cuadrado de la
raz.
13) Escribir un programa que cree una tabla de cadenas que contenga
las palabras en ingls para los nmeros 0 al 9. Utilizando esta tabla, permita
al usuario introducir un dgito y que el programa muestre la palabra
equivalente.
14) Construir un programa que realiza la traduccin de espaol a
ingls con un nmero de trminos indicados por el programador (por ejemplo
10). Para ello inicializar una tabla de cadenas de tres dimensiones:
- nmero de trminos []
- Idiomas [2] (Castellano , Ingls)
- tamao mximo de las cadenas de caracteres [40]
15) Realizar un programa que imprima el contenido de la siguiente
tabla de cadenas, carcter a carcter:
char texto[][40] =
{
"Buscar la belleza" ,
"es la nica razn que" ,
"merece la pena " ,
"en este" ,
"asqueroso Mundo" ,
} ;
Cap.3-16
Captulo 3: Tablas y cadenas ProgramacinenC
16) En estadstica la MODA de un grupo de nmeros es aquel que aparece
con ms frecuencia. Por ejemplo, dada la lista 1, 2, 3, 4, 5, 6, 7, 5, 4, 6,
9, 4 la moda es 4, ya que aparece tres veces. Plantear programa que permita
al usuario introducir una lista de 20 nmeros y despus busque y muestre la
moda.
17) Escribir un programa que permita al usuario introducir cadenas.
Si la cadena es menor de 80 caracteres, el programa debe rellenarla con
puntos. Imprimir (por pantalla) para verificar que se ha alargado
correctamente.
18) Escribir un programa que cuente el nmero de espacios, comas, y
puntos en una cadena. Utilizar switch para clasificar los caracteres.
19) Nos piden desarrollar un programa que ejecute una versin
informatizada del ahorcado. En el juego del ahorcado, se muestra la longitud
de una palabra mgica (utilizando las lneas de subrayado) y se trata de
adivinar de qu palabra se trata, introduciendo letras. Cada vez que se
introduce una letra, se examina la palabra mgica para verificar si contiene
esa letra. Si lo hace, se muestra la letra. Guardar la cuenta del nmero de
letras introducidas para completar la palabra. Por razones de sencillez, un
jugador gana cuando completa la palabra mgica utilizando 15 o menos
suposiciones. Para este ejercicio utilizar la palabra mgica "concatenacin".
20) Modificar el programa anterior para permitir introducir por el
usuario la palabra secreta.
Cap.3-17
CAPTULO 4
Punteros
Captulo 4: Punteros ProgramacinenC
4.1 INTRODUCCIN
El entendimiento y correcto uso de los punteros es crtico para la
creacin correcta de la mayora de los programas en C. Hay tres razones para
ello:
1) Los punteros proporcionan los mecanismos mediante los cuales las
funciones modifican sus argumentos de llamada.
2) Se pueden utilizar para soportar rutinas de asignacin dinmica
de memoria.
3) Pueden sustituir a las tablas en muchas situaciones para
incrementar la eficacia.
Adems de ser una de las caractersticas ms poderosas de C, los
punteros son tambin una de las ms peligrosas. Por ejemplo, usando punteros
sin inicializar o incontrolados se puede provocar la cada del sistema. Quiz
peor, es fcil usar incorrectamente los punteros y provocar errores que son
difciles de encontrar.
Debido a la importancia de los punteros y su potencial abuso, este
captulo los examina con detalle.
4.2 PUNTEROS: DEFINICIN Y OPERADORES (& y *)
Un puntero es una variable que seala una direccin de memoria.
Normalmente esta direccin es una posicin de otra variable en memoria. Aunque
podra ser la direccin de un puerto o de la RAM de propsito general, tal
como la memoria intermedia de vdeo (buffer).
Ilustr. 1 Representacin grfica de un puntero
A los programadores en lenguaje ensamblador les recordar las tcnicas
de direccionamiento indirecto.
Si una variable contiene la direccin de otra variable, entonces se dice
que la primera variable apunta a la segunda. La figura 1 ilustra esta
situacin.
Cap.4-2
Captulo 4: Punteros ProgramacinenC
Si una variable va a contener un puntero, entonces se debe declarar como
tal. El formato general para la declaracin de una variable puntero es:
tipo *nombre_de_variable ;
Donde tipo puede ser cualquier tipo base en C y nombre_de_variable es
el nombre de la variable puntero. El tipo base del puntero define qu tipo de
variables puede sealar el puntero. Por ejemplo, estas sentencias declaran
punteros a enteros y caracteres:
char *p ; /* p sealar a variables que contengan caracteres */
int *temporal, *comienzo ; /* apuntan a enteros */
Los operadores especiales de punteros son: & y * y slo requieren
de un operando (monarios).
& -> Devuelve la posicin de memoria que ocupa la variable que lo
acompaa (operando). Por ejemplo:
guar_dir = &contador ;
Almacena en la variable guar_dir la posicin de memoria del
ordenador donde se guarda el valor de la variable contador.
* -> Es complementario a &. Devuelve el contenido de la direccin que
seala el operando que lo acompaa. Siguiendo con el ejemplo
anterior:
resultado = *guar_dir ;
El significado de esta asignacin es el siguiente: El valor
guardado (*) en la posicin de memoria sealada por guar_dir ser
almacenado en la variable resultado.
Es importante tener muy claro el papel que realizan estos dos operandos
para comprender el potencial de los punteros. Recordar que:
A) variable_1 = 10
B) dir_var_1 = &variable_1
C) variable_2 = *dir_var_1
A) Asigna el valor 10 a una variable denominada variable_1.
B) Guarda en una variable (previamente definida como puntero) la
direccin en la cul se aloja la variable variable_1. No confundir el
trmino <contenido de variable> (en este caso igual a 10) con <posicin
de memoria> que ocupa la variable dentro del sistema.
C) El valor que existe en la posicin sealada por dir_var_1 ser
copiado en la variable_2.
Por desgracia, en C, el signo de multiplicacin y el de "el contenido
de" es el mismo. Al escribir los programas, debe tenerse en cuenta que estos
operadores no tienen relacin.
Aqu se presenta un programa que usa dos sentencias de asignacin dando
a imprimir el nmero 100 en la pantalla:
Cap.4-3
Captulo 4: Punteros ProgramacinenC
#include <stdio.h>
main()
{
int *con_dir, con, valor ;
con = 100 ;
con_dir = &con ; /* toma la direccin de con */
valor = *con_dir ; /* coge el valor de esa direccin */
printf("%d", valor) ; /* visulaliza 100 */
}
Cmo transfiere el compilador el nmero de bytes apropiado para
cualquier asignacin que usa un puntero?. La respuesta es que el tipo base del
puntero determina el tipo de datos que el compilador asumir que seala el
puntero. En el ejemplo anterior con_dir es un puntero que seala a un entero.
El compilador copia dos bytes de informacin en valor desde la direccin que
apunta con_dir. Si con_dir hubiera sido un puntero a double, entonces el
compilador tendra que copiar ocho bytes.
Debe asegurarse que las variables puntero siempre apuntan al tipo
correcto de datos. Por ejemplo, cuando se declara un puntero de tipo int, el
compilador supone que cualquier direccin que guarde el puntero indicar la
posicin de una variable entera. Como C permite asignar cualquier direccin
a una variable de puntero, el siguiente fragmento de cdigo compilar,
mostrando slo un mensaje de aviso ("warning"):
#include <stdio.h>
/* Este programa no funcionar correctamente */
main()
{
float x = 10.1 , y ;
int *p ;
p = &x ;
y = *p ;
printf("%f", y) ;
}
Este programa no asignar el valor de x a y. Como el programa declara
p como un puntero a entero, el compilador transferir slo dos bytes de
informacin a y, y no los cuatro bytes que normalmente constituyen un nmero
en punto flotante.
4.3 ASIGNACIN, ARITMTICA Y COMPARACIN DE PUNTEROS
En general, las expresiones que utilizan punteros conforman las mismas
reglas que cualquier otra expresin de C. Esta seccin examinar unos aspectos
especiales de las expresiones de punteros.
ASIGNACIN DE PUNTEROS
Como cualquier variable, se puede usar un puntero en la parte derecha
de una sentencia de asignacin para copiar el valor del puntero a otro
Cap.4-4
Captulo 4: Punteros ProgramacinenC
puntero, como en el siguiente ejemplo:
#include <stdio.h>
main()
{
int x=200 ;
int *dir_1, *dir_2 ;
dir_1 = &x ;
dir_2 = dir_1 ;
/* imprime el valor hexadecimal de la direccin
de x, NO el valor de x */
printf("la direccin de x es: %p ", dir_2) ;
/* ahora imprime el valor de x */
printf("el valor de x es %d\n", *dir_2) ;
}
Este programa visualiza la direccin, en hexadecimal, de x utilizando
el cdigo de formato %p de printf(), que especifica la visualizacin de una
direccin de puntero.
ARITMTICA DE PUNTEROS
En C se puede usar slo estas operaciones aritmticas sobre un puntero:
+, -, ++ y --
Adems, solamente se pueden sumar o restar cantidades enteras. No se
puede, por ejemplo, sumar un nmero en coma flotante a un puntero.
La aritmtica de punteros difiere de la aritmtica "normal" en un punto
muy importante: se hace en relacin al tipo base del puntero. Cada vez que se
incremente un puntero, sealar al siguiente elemento, atendiendo a su tipo
de base, ms all del que est apuntando actualmente. Para comprender esto,
sea el puntero p1 a un entero con el valor actual (direccin contenida) 2000.
Despus de la expresin:
p1++ ;
El contenido de p1 ser 2002, suponiendo que los enteros tienen una
longitud de 2 bytes. Cada vez que el ordenador incremente p1, sealar al
siguiente entero. Por la misma regla, si p1 hubiera sido un puntero float
(suponiendo float de 4 bytes), entonces el valor resultante contenido en p1
habra sido 2004.
Lo mismo sirve para los decrementos. Por ejemplo:
p1-- ;
Causar que p1 (entero) tenga el valor 1998, si previamente tena 2000.
La nica aritmtica de punteros que aparece como "normal" se produce
cuando se utilizan punteros a char. Dado que los caracteres tienen una
longitud de un byte, un incremento aumenta el valor del puntero en uno, y un
decremento reduce su valor en uno.
Cap.4-5
Captulo 4: Punteros ProgramacinenC
Ilustr. 2 Relacin aritmtica del puntero en funcin del tipo base del puntero
El lenguaje C no limita slo a incrementos o decrementos. Tambin se
pueden sumar o restar enteros a punteros. la expresin:
p1 = p1 + 9 ;
equivale a p1 += 9 ;
Har que p1 seale al noveno elemento del tipo de base de p1 despus del
que apuntaba actualmente.
Se pueden aplicar los operadores de incremento y decremento bien al
propio puntero o al objeto al que apunta. Sin embargo, se debe tener cuidado
cuando se intenta incrementar el objeto (contenido de la posicin de memoria)
al que seala un puntero. Cul es la diferencia de las siguientes
sentencias?:
*p1++ ; /* equivale a *(p1++) */
(*p1)++ ; /* equivale a ++*p1 *p1 += 1 */
En el primer caso la sentencia incrementa la direccin de p1 y despus
obtiene el valor almacenado en la nueva posicin. En el segundo caso se
incrementa el contenido almacenado en la direccin marcada por p1.
Adems de la suma y la resta de un puntero y un entero, no se pueden
realizar otras operaciones sobre punteros; especficamente, no se puede
multiplicar o dividir punteros; no se puede sumar o restar punteros; no se
puede aplicar los operadores de desplazamiento y enmascaramiento y no se puede
sumar o restar los tipos float o double a punteros.
COMPARACIN DE PUNTEROS
Es posible comparar dos punteros en una expresin relacional. Por
ejemplo, dados dos punteros p_1 y p_2, son perfectamente vlidas las
siguientes sentencias:
Cap.4-6
Captulo 4: Punteros ProgramacinenC
if(p_1 < p_2)
printf("p_1 seala a la memoria ms baja que p_2\n") ;
Generalmente se usan las comparaciones de puntero cuando dos o ms
punteros sealan a un objeto comn, como por ejemplo una tabla. Entonces
relaciones como ==, !=, <, >=, etc., funcionarn correctamente.
4.4 ANALOGA ENTRE TABLAS Y PUNTEROS
En C existe una fuerte relacin entre punteros y tablas. Cualquier
operacin que pueda lograrse con la indexacin de una tabla tambin puede
realizarse con punteros. La versin con punteros ser por lo general ms
rpida. La razn es que C tarda ms en indexar una tabla que lo que hace el
operador *. La declaracin:
int a[10] ;
Define la tabla a de tamao 10, esto es, un bloque de 10 elementos
consecutivos llamados: a[0], a[1], ... y a[9].
Ilustr. 3 Estructura de una tabla
La notacin a[n] se refiere al n-simo elemento de la tabla. Si p_dir_a
es un puntero a un entero, declarado como:
int *p_dir_a ;
Entonces la asignacin:
p_dir_a = &a[0] ;
Hace que p_dir_a apunte al elemento cero de a; esto es, contiene la
direccin de a[0]. Ahora la asignacin:
x = *p_dir_a ;
Copiar el contenido de a[0] en x. Si p_dir_a apunta a un elemento
particular de la tabla, p_dir_a + n sealar a n elementos despus de pa, y
pa - n referenciar a n elementos antes. As, si p_dir_a apunta a a[0]:
*(p_dir_a + 1)
Se refiere al contenido de a[1], p_dir_a + n es la direccin de a[n] y
*(p_dir_a + n) es el contenido de a[n].
Ilustr. 4 Puntero a una tabla
Cap.4-7
Captulo 4: Punteros ProgramacinenC
Lo anterior es verdadero sin importar el tipo o tamao de las variables
de la tabla a. Recordar que el significado de "agregar 1 a un puntero", es que
p_dir_a + 1 apunta al siguiente elemento de la tabla y p_dir_a + n apunta al
n-simo elemento delante de p_dir_a.
La correspondencia entre indexacin y aritmtica de punteros es muy
estrecha. Por definicin, el valor de una variable o expresin de tipo tabla
es la direccin del elemento cero de la tabla. As, que despus de la
asignacin:
p_dir_a = &a[0] ;
La variable puntero p_dir_a y a tienen valores idnticos. Puesto que el
nombre de la tabla es un sinnimo para la posicin del elemento inicial, la
asignacin p_dir_a = &a[0] puede escribirse tambin como:
p_dir_a = a ;
Cualquier expresin de tabla e ndice es equivalente a una expresin
escrita como un puntero y un desplazamiento. Por ejemplo &a[i] es idntico a
p_dir_a + n; p_dir_a + n es la direccin del n-simo elemento delante de
p_dir_a.
Existe una diferencia entre nombre de tabla y un puntero, que debe
tenerse en mente. Un puntero es una variable, por esto:
p_dir_a = a y
p_dir_a++
Son legales. Pero un nombre de tabla no es una variable; construcciones
como:
a = p_dir_a y
a++
Son ilegales.
Para ver un ejemplo de la forma en que se pueden usar punteros en lugar
de tablas indexadas, considerar estos programas - uno usa indexacin de tabla
y el otro punteros - que visualizan los contenidos de una cadena en
minsculas:
/* versin tabla */
#include <stdio.h>
#include <ctype.h>
main()
{
char cadena[80] ;
int i ;
printf("introducir una cadena en maysculas: ") ;
gets(cadena) ;
printf("esta es la cadena en minsculas: ") ;
for(i = 0 ; cadena[i] ; i++)
printf("%c", tolower(cadena[i])) ;
}
Cap.4-8
Captulo 4: Punteros ProgramacinenC
/* versin puntero */
#include <stdio.h>
#include <ctype.h>
main()
{
char *p ;
char cadena[80] ;
printf("introducir una cadena en maysculas: ") ;
gets(cadena) ;
printf("esta es la cadena en minsculas: ") ;
p = cadena ; /* coge la direccin de cadena[0] */
while(*p)
printf("%c", tolower(*p++)) ;
}
A veces, los programadores principiantes en C errneamente creen que
nunca deberan usar el indexamiento de tablas porque los punteros son ms
eficaces. Sin embargo, ste no es el caso. Si se quiere acceder a la tabla
estrictamente en orden ascendente o descendente, entonces los punteros son ms
fciles de usar y ms rpidos. Sin embargo, si se quiere acceder a la tabla
aleatoriamente, entonces es mejor la indexacin ya que generalmente ser tan
rpido como la evaluacin de una expresin compleja de puntero y porque es ms
fcil programar y entender. Adems, cuando se usan ndices en la tabla, se le
deja al compilador algunas de las tareas del programador.
PASAR LA DIRECCIN DE UN ELEMENTO DE TABLA A UN PUNTERO
Hasta el momento se ha trabajado con la asignacin de la direccin del
primer elemento de una tabla a un puntero. Sin embargo, se puede asignar la
direccin de un elemento especfico de una tabla aplicando & a una tabla
indexada. Por ejemplo, este fragmento pone la direccin del tercer elemento
de x en p:
p = &x[2] ;
Un sitio en que esta prctica es especialmente til es en encontrar una
"subcadena" (conjunto de elementos dentro de una cadena). Por ejemplo, este
programa imprime el resto de una cadena, que se introdujo por el teclado,
desde el punto en el que el ordenador se encuentra el primer espacio:
Cap.4-9
Captulo 4: Punteros ProgramacinenC
/* Visualiza una cadena tras el primer blanco encontrado */
#include <stdio.h>
main()
{
char c[80], *p ;
int i ;
printf("introduce una cadena: ") ;
gets(c) ;
for(i = 0 ; c[i] && s[i] != ; i++) ;
p = &c[i] ;
printf(p) ; /* ver apartado de punteros y cadenas */
}
INDEXANDO UN PUNTERO
Hasta el momento se viene estudiando como sustituir una tabla por un
puntero, con las ventajas que ello reporta. Sin embargo tambin se puede
realizar el proceso inverso: indexar un puntero como si fuera una tabla. Esta
posibilidad indica de nuevo la estrecha relacin entre punteros y tablas, este
programa es perfectamente vlido e imprime los nmeros 1 a 5 en la pantalla:
/* indexando un puntero como una tabla */
#include <stdio.h>
main()
{
int *p, t ;
int i[5] = {1, 2, 3, 4, 5} ;
p = i ;
for (t = 0 ; t < 5 ; t++)
printf("%d ", p[t]) ;
}
En C la sentencia p[t] es idntica a p + t.
4.5 PUNTEROS Y CADENAS DE CARACTERES
Posiblemente la ms comn de las aplicaciones de las cadenas de
caracteres se encuentra como argumento a funciones como en:
printf("brigada central\n") ;
Cuando una cadena de caracteres como sta aparece en un programa, el
acceso a ella es a travs de un puntero a carcter; printf() recibe un puntero
al principio de la tabla de caracteres. Esto es, tiene acceso a una cadena
constante por un puntero a su primer elemento.
Las cadenas de caracteres no necesitan ser argumentos de funciones. Si
dir_mensaje se declara como carcter:
Cap.4-10
Captulo 4: Punteros ProgramacinenC
char *dir_mensaje ;
Entonces la siguiente sentencia:
dir_mensaje = "Juan Madrid" ;
Asigna a dir_mensaje un puntero a la tabla de caracteres. Es decir,
dir_mensaje no contiene el texto indicado sino que seala al primer carcter
de la cadena. El lenguaje C no proporciona ningn operador para procesar como
unidad una cadena de caracteres.
Existe una importante diferencia entre estas definiciones:
char mensaje[] = "Las apariencias no engaan" ; /* tabla */
char *dir_mensaje = "Las apariencias no engaan" ; /* puntero */
Donde mensaje es una tabla, suficientemente grande como para contener
la secuencia de caracteres y el \0 que lo inicializa. Se pueden modificar
caracteres individuales dentro de la tabla, pero mensaje SIEMPRE se referir
a la misma localidad de almacenamiento. Por otro lado, dir_mensaje es un
puntero, inicializado para sealar a una cadena de caracteres; el puntero
PUEDE modificarse posteriormente para que apunte a algn otro lado, pero el
resultado es indefinido si se trata de modificar el contenido de la cadena.
Ilustr. 5 Cadena de caracteres y puntero a cadena.
Recordar que cuando un ordenador usa una constante de cadena en
cualquier tipo de expresin, trata la constante como un PUNTERO AL PRIMER
CARCTER de la cadena. Por ejemplo, este programa imprime "regalo de la casa"
en la pantalla.
#include <stdio.h>
main()
{
char *s ;
s = "regalo de la casa" ;
printf(s) ;
}
4.6 TABLA DE PUNTEROS
Se pueden hacer tablas de punteros como de cualquier otro tipo de datos.
La declaracin para una tabla de punteros a entero de tamao 10 es:
int *x[10] ;
Cap.4-11
Captulo 4: Punteros ProgramacinenC
Para asignar una direccin de una variable entera llamada var al tercer
elemento de la tabla de punteros, se escribira:
*x[2]
Una utilizacin comn de una tabla de punteros es la de guardar los
punteros a los mensajes de error. Se puede crear una funcin que sacar un
mensaje, dando su nmero de cdigo, como se muestra en serror():
char *err[] =
{
"no puedo abrir el archivo\n" ,
"error de lectura\n" ,
"error de escritura\n" ,
"fallo de soporte\n" ,
} ;
serror(int num)
{
printf("%s", err[num]) ;
}
Como se puede ver, el cdigo llama a printf() en serror() con un puntero
de carcter que apunta a uno de los diversos mensajes de error, indexados por
el nmero de error, que pasa a la funcin. Por ejemplo, si la computadora pasa
el nmero 2 a num, entonces el cdigo provoca que el ordenador visualice el
mensaje error de escritura.
Los nuevos usuarios de C algunas veces se confunden con la diferencia
entre una tabla de dos dimensiones y una de punteros. Dadas las definiciones:
int a[10][20] ;
in *b[10] ;
Entonces tanto a[3][4] como b[3][4] son referencias sintcticas
legtimas a un nico entero. Pero a es verdaderamente una tabla de dos
dimensiones: se le han asignado 200 celdillas de tamao de un entero, y se
emplea el clculo convencional de ndices [fila][columna] para encontrar el
elemento. Para b, sin embargo, la definicin slo asigna 10 punteros y no los
inicializa; la inicializacin debe realizarse de forma EXPLCITA. Suponiendo
que cada elemento de b apunta a una tabla de veinte elementos, entonces
existirn 200 enteros reservados, ms diez celdas para los punteros. La
ventaja ms importante de la tabla de punteros es que los tamaos de las
tablas a las que apunta pueden ser de longitudes diferentes. Esto es, no es
necesario que cada elemento de b apunte a un vector de veinte elementos;
alguno puede apuntar a dos elementos, otro a cincuenta y algn otro a ninguno.
Aunque se est basando esta discusin en trminos de enteros, el uso ms
frecuente de tablas de punteros es para almacenar cadenas de caracteres de
longitudes diversas, como en la declaracin siguiente:
char *DIA[] = {"DIA NO VALIDO", "LUNES", "MARTES", "MIERCOLES",
"JUEVES", "VIERNES", "SABADO", "DOMINGO"} ;
Cap.4-12
Captulo 4: Punteros ProgramacinenC
Ilustr. 6 Tabla de punteros
Puede apreciarse como se optimiza ms el espacio de esta forma que
utilizando una declaracin equivalente con tablas de dos dimensiones:
char DIA[][25] = {"DIA NO VALIDO", "LUNES", "MARTES", "MIERCOLES",
"JUEVES", "VIERNES", "SABADO", "DOMINGO"} ;
Ilustr. 7 Representacin grfica de una tabla bidimensional
4.7 PUNTEROS A PUNTEROS
El concepto de tabla de punteros es directo, ya que la tabla mantiene
su significado claro. Sin embargo se puede confundir los punteros a punteros.
Un puntero a puntero es una forma de direccionamiento indirecto
mltiple, o una cadena de punteros. En la ilustracin siguiente en el caso de
un puntero normal, el valor del puntero es la direccin de variable que
contiene el deseado valor. En el caso de un puntero a puntero, el primer
puntero contiene la direccin del segundo puntero, que apunta a la variable
que contiene el valor deseado.
Se puede llevar direccionamiento indirecto mltiple a cualquier
extensin deseada, pero hay pocos casos donde ms de un puntero a puntero es
necesario, o incluso bueno de usar. La direccin indirecta en exceso es
difcil de seguir y propensa a errores conceptuales. (No confundir
direccionamiento mltiple con listas enlazadas, que se usan en aplicaciones
como las bases de datos).
Se debe declarar una variable que es un puntero a puntero como tal.
Poniendo un asterisco adicional delante del nombre de la variable. Por
ejemplo, esta declaracin le dice al compilador que nueva_era es un puntero
a un puntero de tipo float:
Cap.4-13
Captulo 4: Punteros ProgramacinenC
float **nueva_era ;
Ilustr. 8 Indireccin simple y mltiple
Es importante entender que nueva_era no es un puntero a un nmero en
punto flotante, sino un puntero a un puntero float.
El acceso al valor final que es apuntado indirectamente por un puntero
a puntero requiere aplicar el operador asterisco dos veces como se muestra en
este ejemplo:
#include <stdio.h>
main()
{
int x, *p, **pp ;
x = 10 ;
p = &x ;
pp = &p ;
printf("%d", **pp) ; /* imprime el valor de x */
}
Aqu este cdigo declara p como un puntero a un entero, y pp como un
puntero a un puntero de un entero. La llamada a printf() imprimir el nmero
10 en la pantalla.
Cap.4-14
Captulo 4: Punteros ProgramacinenC
4.8 APLICACIN, ASIGNACIN DINMICA Y DECLARACIONES COMPLICADAS
APLICACIN
El lenguaje C es muy potente en su enfoque a la aritmtica de
direcciones; su integracin de punteros, tablas y aritmtica de direcciones
es uno de los aspectos que le dan fuerza.
Se ilustrar al presentar un rudimentario asignador de memoria. Contiene
dos rutinas:
A) toma_mem().- Devuelve un puntero *dir_tabla a n posiciones
consecutivas, que pueden ser empleadas por el invocador de toma_mem(),
para almacenar caracteres.
B) libera_mem().- Deja libre el almacenamiento adquirido en esta forma,
de modo que pueda ser reutilizado posteriormente. Las rutinas son
rudimentarias, puesto que las llamadas a libera_mem() deben realizarse
en el orden opuesto a las llamadas realizadas a toma_mem().
El almacenamiento manejado por toma_mem() y libera_mem() se comporta
como una PILA o lista del tipo LIFO (ltimo en entrar, primero en salir). La
biblioteca estndar proporciona funciones anlogas llamadas malloc() y free()
que no tiene tales restricciones; en el siguiente apartado se mostrar cmo
se pueden realizar.
La implantacin ms sencilla es hacer que toma_mem() maneje elementos
de una tabla de caracteres que se llamar t_car[]. Esta tabla est reservada
para toma_mem() y para libera_mem(). Puesto que stas hacen su trabajo con
punteros, no con ndices, ninguna otra rutina necesita conocer el nombre de
la tabla, la cual puede ser declarada como static
1
en el archivo fuente que
contiene toma_mem() y libera_mem(), y as ser invisible hacia fuera. En la
implantacin prctica, la tabla puede incluso no tener un nombre; podra
obtenerse llamando a malloc() o pidiendo al sistema operativo un puntero hacia
algn bloque sin nombre de memoria.
La otra informacin necesaria es cunto de t_car[] se ha utilizado.
Empleamos un puntero, denominado *dir_tabla, que seala al siguiente elemento
libre cuando se requieren n caracteres a toma_mem():
1 Revisa si hay suficiente espacio libre en t_car[].
2 Si lo hay, toma_mem() retorna el valor actualizado de *dir_tabla
(esto es, el principio del bloque libre), despus lo incrementa en n
para sealar a la siguiente rea libre. Si no hay espacio, toma_mem()
regresa cero.
En tanto toma_mem(p) simplemente hace que *dir_tabla sea igual a p si
p est dentro de la tabla t_car[].
Grficamente se puede representar como:
1
Modificador de almacenamiento, estudiado en el siguiente captulo, que representa variables
permanentes en su propia funcin y desconocidas fuera de la misma.
Cap.4-15
Captulo 4: Punteros ProgramacinenC
#include <stdio.h>
#define TAM_MAX 100000 /* tamao del espacio disponible */
char *toma_mem(int n) ; /* prototipos de funcin */
void libera_mem(char *p) ;
static char t_car[TAM_MAX] ; /* tabla para toma_mem() */
static char *dir_tabla = ta_car, *dir_t_ant ;
main()
{
....
dir_tabla = toma_mem(100) ;
if (dir_tabla == NULL)
{
printf("asignacin no vlida\n") ;
exit(1) ;
}
....
dir_t_ant = dir_tabla ;
dir_tabla = &t_car[5000] ;
libera_mem (dir_tabla) ;
....
}
char *toma_mem(int n) /* regresa un puntero a n caracteres */
{
if (t_car + TAM_MAX - dir_tabla > = n) /* cabe? */
{
Cap.4-16
Captulo 4: Punteros ProgramacinenC
dir_tabla += n ;
return dir_tabla - n ; /* antigua direccin */
}
else
return NULL ; /* equivale a 0 */
}
void libera_mem(char *p)
{
if (p >= t_car && p < t_car + TAM_MAX)
dir_tabla = p ;
else
dir_tabla = dir_t_ant ;
}
Recordar que en general, un puntero puede ser inicializado tal como
cualquier otra variable, aunque normalmente los nicos valores significativos
son cero o una expresin que involucre la direccin de un dato previamente
definido y de un tipo apropiado. En el siguiente programa, la declaracin:
static char *dir_tabla = t_car ;
Define a *dir_tabla como un puntero a caracteres y lo inicializa para
sealar al principio de la tabla t_car[], que es la siguiente posicin libre
cuando el programa comienza. Esto tambin podra haberse escrito como:
static char *dir_car = &t_car[0] ;
Puesto que el nombre de la tabla es la direccin del primer elemento.
La sentencia:
if (t_car + TAM_MAX - dir_tabla >= n) ...
Comprueba si existe suficiente espacio para satisfacer la peticin de
n caracteres. Si lo hay, el nuevo valor de *dir_tabla sera, como mximo la
ltima celda asignada a la tabla t_car[]. Si la peticin puede satisfacerse,
toma_mem() debe retornar con un puntero al principio de un bloque de
caracteres. De lo contrario, toma_mem() debe regresar con alguna seal de que
no queda espacio. El lenguaje C garantiza que cero nunca es una direccin
vlida para datos y por lo tanto puede usarse un valor de cero como retorno
para sealar un suceso anormal, en este caso, falta de espacio.
Los punteros y los valores enteros no son intercambiables. Cero es la
nica excepcin: la constante cero puede ser asignada a un puntero, y ste
puede compararse contra la constante cero. La constante simblica NULL se
emplea con frecuencia en lugar de cero, como un mnemnico para indicar ms
claramente que es un valor especial para un puntero. NULL est definida en
<stdio.h>.
ASIGNACIN DINMICA
Las estructuras de datos internas son aquellas que residen en la memoria
central del ordenador. De entre ellas, las que se han usado hasta ahora, como
las tablas, pertenecen a la clase denominada estructuras de datos estticas,
las cuales se caracterizan porque su tamao y estructura se fija en tiempo de
compilacin y permanecen inalterables durante todo el tiempo que dure la
ejecucin del programa o subprograma en que han sido declaradas.
Para un determinado tipo de problemas en los que el nmero de datos a
tratar vara durante su proceso, las estructuras estticas no son adecuadas
porque limitan el nmero mximo de datos a almacenar. Por otra parte, si este
nmero de datos decrece, se sigue ocupando el total de la memoria reservada,
aunque no se utilice.
Cap.4-17
Captulo 4: Punteros ProgramacinenC
Algunos lenguajes de programacin, como el C, proporcionan una nueva
clase de estructuras de datos para resolver esta cuestin. Estas, denominadas
estructuras de datos dinmicas, se caracterizan porque su tamao y estructura
pueden ser modificados en tiempo de ejecucin, y la nica limitacin respecto
a su tamao mximo viene impuesta por el tamao de la memoria interna de la
computadora.
Mediante el uso de variables de tipo puntero y funciones especficas de
control de memoria, se pueden crear variables dinmicas en cualquier momento
de la ejecucin de un programa y eliminarlas cuando ya no sean necesarias,
liberando el espacio de memoria que ocupaban. Esto permite un mejor
aprovechamiento de la memoria interna, que es un recurso escaso y limitado.
Con este mtodo, el almacenamiento de la informacin se asigna desde el
rea de memoria libre (usando el modelo de memoria pequeo -small- por
defecto) que se encuentra entre el programa, el rea de almacenamiento
permanente y la pila. La figura siguiente muestra de un modo grfico cmo
aparecera en memoria un programa en C:
Ilustr. 10 Visin conceptual del uso de memoria en un programa de C.
La pila va bajando al mismo tiempo que se utiliza, de modo que la
cantidad de memoria que necesita va determinada por el diseo del programa.
Por ejemplo: un programa con muchas funciones recursivas
2
utilizar
peticiones ms grandes a la memoria de pila que un programa que no tenga este
tipo de funciones. (Recordar que debido a que las variables locales estn
almacenadas en la pila, cada llamada recursiva a una funcin necesita espacio
adicional de pila). La memoria que requiere un programa y los datos globales
2
Una funcin recursiva se caracteriza por realizar llamadas a ella misma.
Cap.4-18
Captulo 4: Punteros ProgramacinenC
(as como las variables declaradas como static -ver siguiente captulo-), est
fija durante la ejecucin del mismo.
La memoria que va a satisfacer la peticin de asignacin dinmica se
toma del rea de memoria libre. Como se puede suponer, es posible que ante
casos muy extremos la memoria llegue a agotarse.
El lenguaje C tiene varias funciones de asignacin dinmica que aportan
flexibilidad. El estndar ANSI propuesto especifica que la cabecera de
informacin necesaria para los funciones relacionadas con la asignacin
dinmica estar en el fichero stdlib.h. Sin embargo el compilador Turbo C
tambin pone la informacin de cabecera de asignacin en alloc.h. Este
documento usar el mtodo propuesto por el estndar ANSI ya que es
transportable.
Las funciones ms utilizadas son:
calloc() ; /* contigous allocate (asigna memoria contigua) */
malloc() ; /* memory allocate (asigna memoria) */
free() ; /* (libera memoria) */
realloc() ; /* (reasigna memoria) */
A continuacin se muestran su prototipo de funcin y su forma de
utilizacin.
#include <stdlib.h>
void *calloc(num_datos, tam_datos) ; /* tam_datos es unsigned */
La funcin calloc() devuelve un puntero a la memoria asignada. La
cantidad de memoria asignada es igual al producto num_datos * tam_datos. As
calloc() asigna suficiente memoria para una tabla de num_datos elementos y de
tamao tam_datos.
En realidad, la funcin calloc() devuelve un puntero a carcter; esto
puede ocasionar un conflicto si se solicita memoria para almacenar una
variable de un tipo distinto a char. Este problema se soluciona utilizando el
operador de moldeado (cast). Para saber el nmero de bytes que se necesitan
para almacenar una variable de un determinado tipo (tam_datos), puede
utilizarse el operador sizeof (nombre del tipo)
3
.
La funcin calloc() devuelve un puntero al primer byte de la regin
asignada. Si no hay suficiente memoria para satisfacer las peticiones, la
funcin devuelve un puntero a NULL (nulo). Siempre es importante verificar qu
valor devolvi, si no es NULL antes de tratar de usarlo.
Ejemplo: Esta funcin devuelve un puntero a una tabla asignada
dinmicamente a 100 elementos de tipo float:
3
Devuelve el n de bytes que necesita el tipo de la variable indicado para almacenarse en memoria.
Cap.4-19
Captulo 4: Punteros ProgramacinenC
#include <stdlib.h>
#include <stdio.h>
float *toma_mem()
{
float *dir_pun ;
dir_pun = (float *) calloc(100, sizeof(float)) ;
if(dir_pun == NULL)
{
printf("asignacin suspendida, fallo\n") ;
exit(1) ;
}
return dir_pun ;
}
#include <stdlib.h>
void *malloc(tamao) ; /* tamao es unsigned */
La funcin malloc() devuelve un puntero al primer byte de una regin de
memoria de tamao tamao que se ha asignado desde la memoria libre para tal
fin. Si no hay suficiente memoria de asignacin dinmica para satisfacer la
peticin, malloc() devuelve un puntero nulo. Siempre es importante verificar
que el valor devuelto no es puntero a NULL antes de tratar de usarlo. Tratando
de usar un puntero a nulo usualmente se producir la cada de todo el
ordenador.
Ejemplo:
A) Peticin de memoria para un entero:
int *dir_pun ;
puntero = (int*) malloc (sizeof(int)) ;
B) Peticin de memoria para un float :
float *sitio ;
sitio = (float *) malloc (sizeof(float)) ;
#include <stdlib.h>
void free(void *puntero) ;
La funcin free() devuelve la memoria que pone puntero dentro de la
memoria libre, haciendo que la memoria est disponible para futuras
asignaciones.
Slo se debe llamar a free() con un puntero que haya sido previamente
asignado utilizando una de las funciones de asignacin dinmica del sistema,
tales como malloc(), o calloc(). La utilizacin de un puntero no vlido en la
llamada probablemente destruya el mecanismo de gestin de memoria y d lugar
a que el sistema caiga.
Ejemplo: Este programa primero asigna espacio para las cadenas
introducidas por el usuario y luego la libera:
Cap.4-20
Captulo 4: Punteros ProgramacinenC
#include <stdio.h>
#include <stdlib.h>
main()
{
char *cadena[100] ;
int i ;
for(i = 0 ; i < 100 ; i++)
{
cadena[i] = (char *) malloc(128) ;
if(cadena[i] == NULL)
{
printf("fallo de asignacin -- terminado\n") ;
exit(1) ;
}
gets(cad[i]) ;
}
for(i = 0 ; i <100 ; i++) /* ahora liberar la memoria */
free(cad[i]) ;
}
#include <stdlib.h>
void *realloc(void *puntero, tamao)
La funcin realloc() cambia el tamao de la memoria apuntada por puntero
que est especificada por tamao. El valor de tamao puede ser mayor o menor
que el original. Devuelve el puntero al bloque de la memoria, ya que puede ser
necesario que realloc() traslade el bloque para incrementar su tamao. Si
ocurre esto, el contenido del antiguo bloque se copia en el nuevo bloque - no
se pierde informacin -. Si no hay suficiente memoria en la memoria de
asignacin dinmica para tamao bytes, devuelve un puntero nulo y el bloque
original se deja sin cambiar. Esto significa que es importante verificar el
xito de una llamada a realloc().
Ejemplo: Este programa asigna 17 caracteres, copia la cadena "esto tiene
16 cs" en ellos y entonces utiliza realloc() para incrementar el tamao a 18
y poner el punto final:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
main()
{
char *p ;
p = (char *) malloc(17) ;
if(p == NULL)
{
printf("error de asignacin -- terminado\n") ;
exit(1) ;
}
strcpy(p, "esto tiene 16 cs") ;
p = realloc(p, 18) ;
Cap.4-21
Captulo 4: Punteros ProgramacinenC
if(p == NULL)
{
printf("error de asignacin -- terminado") ;
exit(1) ;
}
strcat(p, ".") ;
printf(p) ;
free(p) ;
}
DECLARACIONES COMPLICADAS
Al lenguaje C se le reprueba algunas veces por la sintaxis de sus
declaraciones, particularmente las que involucran punteros a funciones. En la
sintaxis hay un intento de hacer que coincidan las variables con su uso;
trabaja bien para casos simples, pero puede ser confusa para las difciles,
debido a que las declaraciones no pueden leerse de derecha a izquierda, y
debido al exceso uso de parntesis. La diferencia entre estas declaraciones:
int *f() ; /* funcin que regresa un puntero a entero */
int (*dir_f)() ; /* puntero a funcin que devuelve un entero */
Refleja el problema: "*" es un operador prefijo y tiene menor
precedencia que "()", de modo que los parntesis son necesarios para obligar
a una asociacin apropiada.
Aunque en la prctica es extrao que aparezcan declaraciones
verdaderamente complicadas, es importante saber cmo entenderlas y, si es
necesario, cmo crearlas. Los que necesiten una leccin de humildad deberan
hojear el texto fuente del sistema operativo UNIX: a lo sumo encontrarn
punteros a funciones.
Una buena forma de sintetizar declaraciones es en pequeos pasos. A
continuacin se muestran algunos ejemplos ilustrativos:
int *f() ; /* f: funcin que devuelve un puntero a entero */
int *f() ; funcin f()
int *f() ; funcin f() que devuelve un puntero a entero
char (*f)() ; /* f: puntero a una funcin */
char (* f)() ; puntero a objeto
char (* f)() ; puntero a funcin que devuelve un carcter
int * f[10] ; /* Tabla de 10 punteros a enteros */
int * f[10] ; Tabla de 10 elementos
int * f[10] ; tabla de 10 punteros a entero
Cap.4-22
Captulo 4: Punteros ProgramacinenC
int (* tabla) [12] ; /* puntero a una tabla de 12 enteros */
int * tabla [12] ; puntero a objeto
int * tabla [12] ; puntero a una tabla de 12 enteros
(* tabla[])() ; /* tabla: tabla de punteros a funciones */
(* tabla [])() ; tabla de un n indefinido de elementos
(* tabla [])() ; tabla de punteros a un objeto
(* tabla [])() ; tabla de punteros a funciones
char *(* f)() ; /* f: puntero a funcin que devuelve un puntero
a carcter */
char *(* f)() ; puntero a objeto
char *(* f)() ; puntero a una funcin
char *(* f)() ; puntero a una funcin que devuelve un puntero a char
char (*(* x())[])() ; /* x: es una funcin que regresa un puntero
a una tabla de punteros a funciones que
devuelven un carcter */
char (*(* x())[])() ; funcin x
char (*(* x())[])() ; funcin que devuelve un puntero
char (*(* x())[])() ; funcin que devuelve un puntero a un objeto
char (*(* x())[])() ; funcin que devuelve un puntero a una tabla de
punteros
char (*(* x())[])() ; funcin que devuelve un puntero a una tabla de
punteros a funciones que regresan un carcter
char (*(* x[3])())[5] ; /* x: tabla, de 3 elementos, de punteros
a una funcin que devuelve un puntero a
una tabla de 5 elementos tipo carcter */
char (*(* x[3])())[5] ; tabla de 3 elementos
char (*(* x[3])())[5] ; tabla de tres punteros a objeto
char (*(* x[3])())[5] ; tabla de tres punteros a funciones
char (*(* x[3])())[5] ; tabla de tres punteros a funciones que devuelven
un puntero a objeto
char (*(* x[3])())[5] ; tabla de tres punteros a funciones que devuelven
un puntero a una tabla de 5 elementos
Cap.4-23
Captulo 4: Punteros ProgramacinenC
4.9 PROBLEMAS CON PUNTEROS
Nada dar ms problemas que un puntero "incontrolado". Los punteros son
un arma de dos filos. Ofrecen un tremendo poder, y son necesarios para muchos
programas. Pero si un puntero contiene accidentalmente un valor errneo, puede
ser el fallo ms difcil de depurar.
Un error de puntero es difcil de encontrar porque el propio puntero no
es problema: el problema es que, cada vez que se realiza una operacin, sta
usa un puntero que est leyendo o escribiendo algn trozo de memoria
DESCONOCIDO. Si se lee una porcin de memoria, lo peor que puede suceder es
que se coja una informacin equivocada. Sin embargo, si se escribe a una zona
de memoria, se escribir sobre otros bloques del cdigo de datos. Este error
no se podra mostrar hasta despus de la ejecucin del programa, y puede
conducir a buscar el error en un lugar equivocado. En ese momento, hay pocas
o ninguna evidencia que sugiera al puntero como origen del problema. Este tipo
de error ha causado la prdida del sueo a los programadores. Como los errores
de punteros son tales pesadillas, nunca se debera generar una.
Los problemas de los punteros se derivan de no entender el
direccionamiento indirecto y los operadores de punteros y los que se
desarrollan del uso accidental de punteros invlidos. La solucin al primer
problema es entender el lenguaje C; y al segundo es siempre verificar la
validez de un puntero antes de usarlo.
A continuacin se muestra un error tpico que cometen los programadores
principiantes de C. (No probar este cdigo a menos que se quiera la cada
del sistema !!!).
#include <stdio.h>
#include <stdlib.h>
main() /* este programa es errneo */
{
char *p ;
*p = malloc(100) ; /* esta lnea es errnea */
gets(p) ;
printf(p) ;
}
Este programa cascar el ordenador sobrescribiendo la tabla de vector
de interrupcin que ayuda a controlar el sistema operativo !. La razn para
este paro es que el cdigo no asigna p a la direccin que devuelve malloc(),
sino a la posicin de memoria a la que apunta p, que es 0 (si el compilador
asigna ese valor inicial a la variable *p al ser declarada). Esto no es
ciertamente lo que se quera. Para corregir este programa, se debe sustituir:
p = malloc(100) ; /* esto es correcto */
Por la lnea errnea.
El programa tambin tiene un segundo, y ms insidioso, error. No hay
comprobacin en tiempo de ejecucin de la direccin que devuelve malloc().
Recordar, si la memoria es escasa, entonces malloc() devolver NULL, que nunca
es un puntero vlido para C. El malfuncionamiento que este tipo de errores
causa es difcil de encontrar porque ocurren raramente, cuando falla una
peticin. La mejor manera de manejar este error es prevenirlo. Esta es una
versin corregida del programa, que incluye una condicin para la validacin
del puntero:
Cap.4-24
Captulo 4: Punteros ProgramacinenC
#include <stdio.h>
#include <stdlib.h>
main() /* este programa es correcto ahora */
{
char *p ;
p = malloc(100) ; /* esto es correcto */
if(p == NULL)
{
printf("fuera de memoria\n") ;
exit(1) ;
}
gets(p) ;
printf(p) ;
}
Recordar que el gran problema de los punteros mal tratados es que son
difciles de seguir. Si se hacen asignaciones a una variable puntero que no
contenga una direccin de puntero vlida, entonces el programa puede parecer
correcto en algunos momentos y fallar en otros. Cuando ms pequeo es el
programa, funcionar ms correctamente, incluso con punteros incorrectos,
porque usa poca memoria y los problemas con ella sern estadsticamente
pequeos. Cuando crece el programa, los fallos sern ms corrientes - pero se
pensar que se deben a las adiciones o cambios en el programa y no a errores
de puntero -.
El signo de un problema de puntero es que los errores tienden a ser
errticos. El programa funcionar bien unas veces e incorrectamente otras. A
veces otras variables contendrn datos inexplicables por razones no conocidas.
Si estos problemas ocurren, comprobar los punteros. Como procedimiento, se
deberan comprobar siempre los punteros cuando se da un error.
Como consuelo, recordar que, mientras los punteros pueden ser
problemticos, son uno de los aspectos ms poderosos y tiles de lenguaje C
y son valiosos cualesquiera que sean los problemas que pueden ocasionar. Hacer
un esfuerzo para aprender a usarlos correctamente.
Un punto final a recordar sobre los punteros es que se deben inicializar
antes de usarlos. Considerar el siguiente fragmente de cdigo:
int *x ;
*x = 100 ;
El uso de este cdigo ser un desastre porque no se sabe dnde apunta
x y si se asigna un valor a la posicin desconocida, que probablemente
destruir algo de valor - como otro cdigo o datos del programa -.
Cap.4-25
Captulo 4: Punteros ProgramacinenC
EJERCICIOS PROPUESTOS, CAPTULO 4
1) Qu es un puntero?. Cules son los operadores puntero y qu
efectos tienen?.
2) Porqu es importante el tipo de base de un puntero?.
3) Cul es la ventaja de utilizar punteros en lugar de indexar
tablas?.
4) Qu se genera cuando se utiliza un nombre de tabla sin ndice?.
5) Es correcto este fragmento?. Si es correcto, explicar porqu
funciona:
char *dir_p = "esto es una cadena" ;
6) Escribir un programa que por medio de un bucle for cuente de 0 a
9, mostrando los nmeros en pantalla. Imprimir los nmeros utilizando un
puntero.
7) Es correcto este fragmento?, porqu?.
int cuenta[10] ;
.
.
.
cuenta = cuenta + 2 ;
8) Qu muestra este fragmento?
int varios[5] = {10, 19, 23, 8, 9} ;
int *dir_p ;
dir_p = varios ;
printf("%d", *(dir_p + 3)) ;
9) Escribir un programa que introduzca una cadena. Despus hacer que
el programa busque el primer espacio. si encuentra uno, que imprima el resto
de la cadena.
10) Escribir un programa, utilizando punteros, que no pare de leer
cadenas hasta que se introduzca STOP.
11) Crear una "ayuda de decisin ejecutiva". Esto es un programa que
contesta si, no o quizs a una pregunta introducida por teclado.
Para crear este programa debe utilizarse una tabla de punteros a
carcter e inicializarlos para que apunten a estas tres cadenas:
"si"
"no"
"quizs - repita pregunta"
Despus, introducir la pregunta del usuario y encontrar la longitud de
la cadena. Luego, utilizar esta frmula para calcular un ndice en la tabla
de punteros:
indi = longitud % 3
Cap.4-26
Captulo 4: Punteros ProgramacinenC
12) Escribir un programa que asigne un valor a un entero utilizando
un puntero a entero. Antes de que termine el programa, que muestre las
direcciones de la variable entera, el puntero, y el puntero a puntero
(recordar, %p muestra el valor de un puntero).
13) Una de las funciones estndar de biblioteca de C se llama puts();
escribe un argumento de cadena en la pantalla seguido de un salto de lnea.
Realizar un programa con punteros que sea una versin de puts() llamada
miputs().
14) Escribir una versin particular de strlen() llamada mistrlen() y
probarla en un programa.
15) Escribir una versin personalizada de strcat() llamada mistrcat(),
y aplicarla en un programa que lo demuestre.
16) Escribir un programa que pase un puntero a una variable entera a
una funcin. Dentro de la funcin asigne a la variable el valor -1. Despus
de volver de la funcin, demuestre que la variable realmente contiene -1,
imprimiendo su valor.
Cap.4-27
CAPTULO 5
Tipos de datos:
Definidos por usuario y Avanzados
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
5.1 INTRODUCCIN
El lenguaje C permite crear cinco clases diferentes de tipos de datos
personalizados. El tipo primero es el estructurado, que es un grupo de
variables bajo un nombre. El segundo es campo de bit, que es una variacin del
estructurado y permite un acceso fcil a los bits de una palabra. El tercer
tipo es la unin, que habilita para definir el mismo trozo de memoria como dos
o ms tipos diferentes de variables. Un cuarto tipo de datos es la
enumeracin, que es una lista de smbolos. El tipo final se crea usando la
definicin de tipo y simplemente crea un nuevo nombre para tipo de dato
existente.
Para finalizar el primer bloque se estudiar la palabra clave sizeof.
En la segunda parte del captulo se hablar de los tipos de datos
avanzados.
Hasta el momento, slo se han usado los cinco tipos de datos bsicos.
Aunque estos tipos son suficientes para muchas situaciones de programacin,
no pueden satisfacer todas las demandas de los programadores ms exigentes.
El compilador permite ampliar varios modificadores a los tipos bsicos. Entre
ellos pueden encontrarse los siguientes modificadores:
- Modificadores de acceso.
. modificador const
. modificador volatile
- Modificadores de almacenamiento.
. auto
. extern
. static
. register
5.2 CONCEPTOS BSICOS SOBRE ESTRUCTURAS
Una estructura es una coleccin de una o ms variables, de tipos
posiblemente diferentes, agrupadas bajo un solo nombre para manejo
conveniente. Las estructuras ayudan a organizar datos complicados, en
particular dentro de programas grandes, debido a que permiten que un grupo de
variables relacionadas se les trate como una unidad en lugar de como entidades
separadas.
Un ejemplo tradicional de estructura es el registro de una nmina; un
empleado est descrito por un conjunto de atributos, como nombre, domicilio,
nmero de la seguridad social, salario, etc. Algunos atributos pueden, a su
vez, ser estructuras: un nombre tiene varios componentes, como los tiene un
domicilio y an un salario. Otro ejemplo, ms tpico en C, procede de los
grficos: un punto es un par de coordenadas. Un rectngulo se puede obtener
a partir de dos puntos, y otros casos semejantes.
El formato general de una definicin de estructura es:
struct nombre_tipo_estructura
{
tipo nombre_elemento_1 ;
tipo nombre_elemento_2 ;
....
tipo nombre_elemento_n ;
}
var_estructurada_1 , var_estructurada_2 , ... , var_estructurada_n ;
Cap.5-2
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Donde la palabra reservada struct presenta la declaracin de una
estructura (que es una lista de declaraciones entre llaves) cuyo nombre
- optativo - es nombre_de_estructura. El nombre identifica este tipo de
estructura, y en adelante puede ser utilizado como una abreviatura para la
parte de declaraciones entre llaves.
Las variables nombradas dentro de la estructura se llaman miembros.
Estn separadas por punto y coma, lo cual especifica que una definicin de una
estructura es una sentencia. Un miembro de estructura y una variable ordinaria
(esto es, no miembro) pueden tener el mismo nombre sin conflicto, puesto que
siempre se pueden distinguir por el contexto. Adems en diferentes estructuras
pueden encontrarse los mismos nombres de miembros.
La llave que cierra la estructura puede ser seguida - opcionalmente -
por una lista de variables: var_estructura_1... como se hace para cualquier
tipo bsico. Esto es:
struct {...} x, y, z ;
Es sintcticamente anlogo a
int x, y, z ;
En el sentido de que cada proposicin declara a x, y y z como variables
del tipo nombrado y causa que se les reserve espacio contiguo.
Como ejemplos se definirn algunas estructuras propias para grficos.
El objeto bsico es un punto, del cual se supone que tiene una coordenada x
y una coordenada y, ambas enteras:
Los dos componentes pueden ser colocados en una estructura declarada
as:
struct punto
{
int eje_x ;
int eje_y ;
} ;
Tambin podra declararse como:
struct punto
{
int eje_x, eje_y ;
} ;
Cap.5-3
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Una declaracin de estructura que no est seguida por una lista de
variables no reserva espacio de almacenamiento sino que simplemente describe
la forma de la estructura. Sin embargo, si la declaracin de estructura ya
est realizada, su formato se puede emplear posteriormente para crear copias
de esa estructura. Por ejemplo, dada la declaracin anterior punto:
struct punto pt ;
Define una variable pt que es una estructura de tipo struct punto. Una
estructura se puede inicializar al seguir su definicin con una lista de
inicializadores, cada uno una expresin constante, por los miembros:
struct punto max_punto = { 320 , 200 } ;
Una estructura automtica
1
tambin se puede inicializar por asignacin
o llamando a una funcin que regresa una estructura del tipo adecuado.
REFERENCIA DE UN ELEMENTO PERTENECIENTE A UNA ESTRUCTURA
Se referencian elementos individuales de la estructura usando el
operador ".", que se llama a veces operador punto. El formato general es:
nombre_estructura.elemento
El nombre de la variable estructura, que se sigue por un punto, y el
nombre del elemento referenciar ese elemento de la estructura
individualmente.
Por ejemplo para imprimir las coordenadas del punto pt se empleara la
siguiente construccin:
printf("%d, %d", pt.eje_x, pt.eje_y) ;
Las estructuras pueden anidarse. Una representacin de un rectngulo es
como un par de esquinas que denotan las esquinas diagonalmente opuestas:
struct rect
{
struct punto punto_1 ;
struct punto punto_2 ;
} ;
La estructura rect contiene dos estructuras punto. Si declaramos ventana
como:
struct rect ventana ;
Entonces
ventana.punto_1.eje_x
Se refiere a la coordenada eje_x del miembro punto_1 de ventana.
1
El modificador auto (para declarar una estructura automtica) se abordar en la segunda parte del
captulo
Cap.5-4
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Se puede asignar el contenido de una variable de estructura otra
variable de estructura del mismo tipo. Por ejemplo, el siguiente fragmento es
perfectamente vlido:
struct tipo_s
{
int a ;
float b ;
}
var_1, var_2 ;
var_1.a = 10 ;
var_1.b = 100,56 ;
var_1 = var_2 ;
Despus de ejecutar este fragmento, var_2 contendr exactamente lo mismo
que var_1.
TABLAS DE ESTRUCTURAS
Quiz el uso ms comn de las estructuras es una tabla de estructuras.
Para declarar una tabla de estructuras, primero se debe definir una estructura
y despus declarar una tabla con este tipo de variable. Por ejemplo, se define
la estructura datos:
struct datos
{
char nombre[30] ;
char calle[40] ;
char ciudad[20] ;
char estado[3] ;
unsigned long int cod_postal ;
}
Para declarar una tabla de cien elementos de la estructura datos, se
debera escribir:
struct datos empleado_datos[100] ;
Este cdigo crea cien conjuntos de variables que se organizan como se
defini en la estructura datos.
Cap.5-5
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Para acceder a una estructura especfica, se indexa el nombre de la
estructura. Por ejemplo, para imprimir el cdigo postal de la estructura tres,
se escribira:
printf("%u", empleado_datos[2].cod_postal) ;
Como todas las tablas, las tablas de estructuras comienzan el ndice en
cero.
5.3 ESTRUCTURAS Y FUNCIONES
Las nicas operaciones permitidas sobre una estructura son copiarla o
asignarla como unidad, tomar su direccin con el operador &, y tener acceso
a sus miembros. La copia y la asignacin incluyen pasarlas como argumentos a
funciones y tambin retornar con valores de funciones. Las estructuras no se
pueden comparar. Una estructura se puede inicializar con una lista de valores
constantes de miembros; una estructura (automtica) se puede inicializar con
una asignacin.
Hay por lo menos tres mtodos para relacionar estructuras y funciones,
a saber:
- Pasar separadamente los componentes de una estructura a la funcin.
- Pasar una estructura completa.
- Pasar un puntero a una estructura. (Ver apartado 5.4).
Una funcin puede devolver una estructura. A continuacin se explican
los dos primeros apartados acompaados de ejercicios aclaratorios.
PASO DE ELEMENTOS DE ESTRUCTURAS A FUNCIONES
Cuando se pasa un elemento de una variable de estructura a una funcin,
realmente se pasa el valor de ese elemento. As que se est transfiriendo una
variable simple (a menos que ese elemento sea complejo, como una matriz de
caracteres).
Por ejemplo, considerar esta estructura:
struct
{
char x ;
int y ;
float z ;
char s[10] ;
}
ejemplo ;
Esta es el modo en que se pueden pasar cada elemento a una funcin:
func1(ejemplo.x) ; /* pasa el valor del carcter x */
func2(ejemplo.y) ; /* pasa el valor entero de y */
func3(ejemplo.z) ; /* pasa el valor float de z */
func4(ejemplo.s) ; /* pasa el valor de la cadena s */
func5(ejemplo.s[2]) ; /* pasa el valor de carcter de s[2] */
Sin embargo para pasar la direccin de un elemento individual de
Cap.5-6
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
estructura de forma que se consiga pasar un parmetro por referencia
2
, se
pondra el operador & antes del nombre de variable. Por ejemplo, para pasar
las direcciones de los elementos en la variable de estructura ejemplo, se
escribira:
func1(&ejemplo.x) ;
func2(&ejemplo.y) ;
func3(&ejemplo.z) ;
func4(&ejemplo.s) ;
func5(ejemplo.s[2]) ;
Debe fijarse que el operador & precede al nombre de la variable de
estructura y no al nombre del elemento individual. Adems, fjese que el
elemento de la cadena s ya significa una direccin de forma que el uso de &
no se requiere en ese lnea de cdigo.
Otro modo para llevar valores a elementos de una estructura a travs de
una funcin sera la representada en el siguiente cdigo que utiliza la
estructura punto vista anteriormente:
/* hace_punto: crea un punto con las coordenadas x, y */
struct punto hace_punto(int x, int y)
{
struct punto temporal ;
temporal.x = x ;
temporal.y = y ;
return temporal ;
}
La funcin hace_punto(), toma dos enteros y retorna una estructura de
tipo punto donde se encuentran ya expresados los valores de inicializacin.
Ntese que no hay conflicto entre el nombre del argumento y el miembro
con el mismo nombre; incluso la reutilizacin de los nombre refuerza el
vnculo.
La funcin hace_punto ahora se puede usar para inicializar dinmicamente
(con diferentes valor segn usuario) cualquier estructura, o para proporcionar
los argumentos de la estructura a una funcin:
Recordar que:
struct punto
{
int eje_x , eje_y ;
} ;
y
struct rect
{
struct punto punto_1 , punto_2 ;
} ;
2
La llamada por referencia se describe en el captulo 6 - PROFUNDIZACIN DE LAS FUNCIONES - y
bsicamente consiste en la modificacin de la variable desplazada a la funcin, dentro de esta.
Cap.5-7
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
struct rect pantalla ;
struct punto mitad ;
struct punto hace_punto(int, int) ;
pantalla.punto_1 = hace_punto(0,0) ;
pantalla.punto_2 = hace_punto(MAYOR_X , MAYOR_Y) ;
mitad = hace_punto((pantalla.punto_1.eje_x + pantalla.punto_2.eje_x)/2,
(pantalla.punto_1.eje_y + pantalla.punto_2.eje_y)/2);
PASO DE ESTRUCTURAS COMPLETAS A FUNCIONES
Cuando se usa una estructura como argumento para una funcin, C pasa la
estructura entera usando el mtodo estndar de llamada por valor. Este mtodo
significa que cualquier cambio del contenido de la estructura dentro de la
funcin a la que se pasa dicha estructura no afecta a la estructura que se usa
como argumento.
La consideracin ms importante a tener en cuenta cuando se usa una
estructura como parmetro es que el tipo de argumento debe coincidir con el
tipo del parmetro. Por ejemplo, este programa declara el argumento argu y el
parmetro para con el mismo tipo de estructura:
#include <stdio.h>
main()
{
struct
{
int a, b ;
char ch ;
}
argu ;
argu.a = 1000 ;
fun1(argu) ;
}
fun1( struct
{
int x, y ;
char ch ;
}
para )
{
printf("%d", para.x) ;
}
Como se puede observar, este programa imprimir "1000" en la pantalla.
Aunque no es comn ver declaraciones paralelas de estructura de este tipo, un
mtodo ms comn - y que precisa menos esfuerzo - es definir una estructura
globalmente y despus usar su nombre para declarar variables y parmetros de
estructura que se necesite. Usando este mtodo, el mismo programa es:
Cap.5-8
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
#include <stdio.h>
struct ejemplo /* define un tipo de estructura */
{
int a, b ;
char ch ;
} ;
main()
{
struct ejemplo argu ; /* declara argu */
argu.a = 1000 ;
fun1(argu) ;
}
fun1(struct ejemplo para)
{
printf("%d", para.a) ;
}
No slo ahorra esta versin esfuerzo de programacin, sino lo que es ms
importante, ayuda a asegurarse que los argumentos y los parmetros coinciden.
Adems crea la idea de que al leer el programa, para y argu son del tipo
ejemplo.
Para ofrecer diferentes ejemplos en el paso de estructuras a funciones
se muestra el siguiente conjunto de funciones que permite hacer operaciones
aritmticas sobre los puntos sealados por la estructura punto:
/* suma_puntos: suma dos puntos */
struct punto suma_puntos(struct punto p_1, struct punto p_2)
{
p_1.eje_x += p_2.eje_x ;
p_1.eje_y += p_2.eje_y ;
return p_1 ;
}
Aqu, tanto los argumentos como el valor de retorno son estructuras.
Incrementando los componente en p_1 en lugar de utilizar explcitamente una
variable temporal para hacer nfasis en que los parmetros de la estructura
son pasados por valor como cualquiera otros.
Como otro ejemplo, la funcin prueba si un punto est dentro de un
rectngulo, donde se ha adoptado la convencin de que un rectngulo incluye
sus lados izquierdo e inferior pero no sus lados superior y derecho:
/* p_den_rect: retorna 1 si p est en r, 0 si no lo est */
int p_den_rect(struct punto p, struct rect r)
{
return p.eje_x >= r.punto_1.eje_x && p.eje_x < r.punto_2.eje_x &&
p.exe_x >= r.punto_1.eje_y && p.eje_x < r.punto_2.eje_y ;
}
Esto supone que el rectngulo est representado en una forma estndar
en donde las coordenadas punto_1 son menores que las coordenadas punto_2. La
siguiente funcin regresa un rectngulo en forma cannica:
#define min(a, b) ( (a) < (b) ? (a) : (b) )
#define max(a, b) ( (a) > (b) ? (a) : (b) )
Cap.5-9
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
/* rect_canon: pone en forma cannica las coordenadas de un rect. */
struct rect rect_canon(struct rect r)
{
struct rect temporal ;
temporal.punto_1.eje_x = min(r.punto_1.eje_x,r.punto_2.eje_x)
temporal.punto_1.eje_y = min(r.punto_1.eje_y,r.punto_2.eje_y)
temporal.punto_2.eje_x = min(r.punto_1.eje_x,r.punto_2.eje_x)
temporal.punto_2.eje_y = min(r.punto_1.eje_y,r.punto_2.eje_y)
return temporal ;
}
5.4 PUNTEROS A ESTRUCTURAS
El lenguaje C permite punteros a estructuras de la misma forma que los
permite a cualquier otro tipo de variable. Sin embargo, hay algunos aspectos
especiales de los punteros de estructura que deben aclararse.
Se declara un puntero de estructura poniendo el signo "*" delante del
nombre de la variable de estructura. Por ejemplo, si se supone la estructura
rect definida antes, lo siguiente declara apunta_rect para puntero de datos
de ese tipo:
struct rect *apunta_rect ;
Hay dos usos principales para punteros estructurados.
A) Conseguir una llamada por referencia a una funcin (ver captulo 6).
B) Crear listas enlazadas y otras estructuras dinmicas de datos usando
el sistema de asignacin de C.
Hay una desventaja principal pasando todo en vez de estructuras ms
simples a las funciones: es el recargo de tiempo para poner (y quitar) todos
los elementos de la pila. En las estructuras simples con pocos elementos, este
recargo no es importante, pero si una estructura usa varios elementos o si
alguno de ellos son tablas, entonces el rendimiento del tiempo de ejecucin
puede degenerar a niveles inaceptables. Dicho de otra forma, si una estructura
grande va a ser pasada a una funcin, generalmente es ms eficiente pasar un
puntero que copiar la estructura completa.
Cuando se pasa un puntero de estructura a una funcin, el ordenador pone
(y quita) slo la direccin de la estructura en la pila. Esto significa que
C puede ejecutar una llamada ms rpida de la funcin. Tambin, como la
funcin referenciar la estructura real, y no una copia, la funcin puede
modificar los contenidos de los elementos de la estructura usada en la
llamada.
Para encontrar la direccin de una variable de estructura, se pone el
operador "&" antes del nombre de variable de estructura. Por ejemplo, dando
el fragmento siguiente:
struct balance
{
float balan_c ;
char name[80] ;
}
persona ;
struct balance *dir_balance ; /* declara un puntero de estructura */
Se ver que este cdigo:
Cap.5-10
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
dir_balance = &persona ;
Pone la direccin de persona en el puntero dir_balance. Para referenciar
el elemento balan_c, se escribira:
(*dir_balance).balan_c
Los parntesis son necesarios debido a que la precedencia del operador
miembro de estructura "." es mayor que la de "*". La expresin
*dir_balance.balan_c significa *(dir_balance.balan_c), lo cual es ilegal
debido a que balan_c no es un puntero.
EL OPERADOR ->
Los punteros a estructuras se usan con tanta frecuencia que se ha
proporcionado una notacin alternativa como abreviacin. Si p es un puntero
a estructura, entonces:
p->miembro_de_estructura
Se refiere al miembro en particular. (El operador -> es un signo menos -
seguido inmediatamente por >). De esta manera se podra escribir:
printf("el balance es: %f\n", dir_balance->balan_c) ;
/* equivale a: printf("el balance es: %f\n", (*dir_balance).balan_c */
Tanto "." como "->" se asocian de izquierda a derecha, de modo que si
tenemos:
struct rect r, *rp = r ;
Entonces estas cuatro expresiones son equivalentes:
r.punto_1.eje_x
rp->punto_1.eje_x
(r.punto_1).eje_x
(rp->punto_1).x
Los operadores de estructuras "." y "->", junto con "()" para llamadas
a funciones y "[]" para subndices, estn en la parte superior de la jerarqua
de precedencias y se asocian estrechamente. Por ejemplo, dada la declaracin:
struct
{
int longitud ;
char *cadena ;
}
*dir_p ;
Entonces:
++dir_p->longitud
Incrementa a longitud, no a dir_p, puesto que los parntesis implcitos
son ++(p->longitud).
Los parntesis se puede emplear para alterar la asociacin:
(++dir_p)->longitud
Cap.5-11
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Que incrementa a dir_p antes de tener acceso a longitud, y:
(dir_p++)->longitud
Incrementa a dir_p despus del acceso. (Este ltimo conjunto de
parntesis es innecesario).
Del la misma manera:
*dir_p->cadena
Obtiene cualquier cosa a la que cadena apunte;
*dir_p->cadena++
Incrementa a cadena despus de hacer el acceso a lo que seala;
(*dir_p->cadena)++
Incrementa el contenido indicado por cadena;
*p++->cadena
Incrementa a dir_p despus de hacer el acceso al contenido referenciado
por cadena.
5.5 TABLAS Y ESTRUCTURAS EN ESTRUCTURAS
Un elemento de una estructura puede ser simple o compuesto. Un elemento
simple es uno de los tipos de datos incorporados, como un entero o un
carcter. Un elemento compuesto pueden ser: tablas de una o varios
dimensiones, otros tipos de datos y estructuras.
Un ordenador trata un elemento de estructura que es una tabla como se
podra esperar en funcin de los ejemplos anteriores. Por ejemplo, considerar
esta estructura:
struct emp
{
int a[10][10] ; /* tabla de 10 * 10 elementos */
float b ;
}
y ;
Para referenciar al entero 3,7 en a de la estructura y, se escribira:
y.a[3][7]
Tal y como se ha visto al principio, cuando una estructura es un
elemento de otra estructura, se denomina estructura anidada. Por ejemplo, el
elemento address de la variable estructura se anida en emp en este ejemplo:
struct emp
{
struct direc address ;
float salario ;
}
trabajador ;
Aqu direc es la estructura que se defini previamente. El ejemplo
define la estructura emp tomando dos elementos. El primer elemento es la
estructura de tipo direc que contendr una direccin de un empleado. El
segundo elemento es salario, que guarda el salario del empleado. El siguiente
Cap.5-12
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
fragmento de cdigo asignar el cdigo de correo 98765 para el campo cdigo
de la direccin address del trabajador:
trabajador.address.cdigo = 98765 ;
Como se puede ver, este fragmento referencia los elementos de cada
estructura de izquierda a derecha desde la parte externa hasta la ms interna.
5.6 CAMPOS DE BITS
Al contrario de la mayora de los lenguajes de ordenador. C tiene un
mtodo predefinido para acceder a un nico bit en un byte. Este mtodo puede
ser utilsimo por una serie de razones:
1) Si el almacenamiento es limitado, se pueden almacenar varias
variable lgicas (verdadero o falso) en un byte.
2) Ciertas interfaces de dispositivo transmiten informacin que se
codifica en bits en un byte (por ejemplo las seales de control
enviadas por la impresora).
3) Ciertas rutinas necesitan acceder a los bits de un byte.
Aunque se pueden realizar todas estas operaciones con operadores de modo
bit: operadores de desplazamiento (<<, >>), enmascaramiento (&, |) y
complemento (~). Un campo de bit puede aadir ms estructuracin y eficacia
al cdigo. El campo de bit puede tambin hacer menos transportable un
programa.
El mtodo que C usa para acceder a los bits se base en la estructura.
Un campo de bits es justamente un tipo especial de estructura que define la
longitud en bits que tendr cada elemento. El formato general de una
definicin de campo de bits es:
struct nombre_de_estructura_de_tipo
{
tipo nombre_1 : longitud ;
tipo nombre_2 : longitud ;
...
tipo nombre_3 : longitud ;
} ;
Se debe declarar un campo de bit como int, unsigned o signed. Se debera
declarar los campos de bits de longitud 1 como unsigned porque un bit nico
no puede tener signo. cada nombre_n es opcional.
Para ver un ejemplo de campos de bits en accin, utilizar la funcin de
biblioteca biosequip() que tiene este prototipo:
int biosequip(void) ;
El prototipo de biosequip() se encuentra en <bios.h>.
La funcin biosequip() devuelve la lista del equipo instalado en el
ordenador como un valor entero de 16 bits, y queda codificado de la siguiente
manera:
Cap.5-13
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Bit Equipo
0 Debe cargarse inicialmente desde el disquete
1 Instalado inicialmente el coprocesador matemtico
2, 3 Tamao de la RAM en placa BASE.
4, 5 Modo de vdeo inicial
6, 7 Nmero de unidades de disco
8 Instalado el chip de Acceso Directo a Memoria (DMA)
9, 10, 11 Nmero de puertas en serie
0 0 0 = cero
0 0 1 = uno
...
1 1 1 = siete
12 Instalado el adaptador de juegos
13 Instalada la impresora en serie (slo en PCjr)
14, 15 Nmero de impresoras
0 0 = cero
0 1 = una
1 0 = dos
1 1 = tres
Puede representarse como un campo de bits cuando se utiliza la siguiente
estructura:
struct equip
{
unsigned floppy_boot : 1 ;
unsigned copro8087 : 1 ;
unsigned ram_madre : 2 ;
unsigned modo_video : 2 ;
unsigned disqueteras : 2 ;
unsigned dma : 1 ;
unsigned puertos_serie : 3 ;
unsigned adapta_juegos : 1 ;
unsigned : 1 ;
unsigned num_impresoras : 2 ;
}
El siguiente programa utiliza el campo de bit para visualizar el nmero
de unidades de disco flexible y el nmero de puertos serie:
#include <stdio.h>
#include <bios.h>
main()
{
struct equip
{
unsigned floppy_boot : 1 ;
unsigned copro8087 : 1 ;
unsigned ram_madre : 2 ;
unsigned modo_video : 2 ;
unsigned disqueteras : 2 ;
unsigned dma : 1 ;
unsigned puertos_serie : 3 ;
unsigned adapta_juegos : 1 ;
unsigned : 1 ;
unsigned num_impresoras : 2 ;
}
eq ;
int *i ;
i = (int *) &eq ;
Cap.5-14
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
*i = biosequip() ;
printf("%d discos flexibles\n", eq.disqueteras + 1) ;
printf("%d puertos serie\n", eq.puertos_serie) ;
}
Al puntero de enteros i se le asigna la direccin eq. y el valor
devuelto de biosequip() se le asigna a eq, por medio de este puntero. Esto es
necesario debido a que C no permite que un entero sea (cast) dentro de una
estructura. Sin embargo, se ver una solucin mejor a este problema dentro
apartado referente a uniones.
En el programa anterior, se accede a un campo de bit utilizando un
operador punto ".". Sin embargo, recordar que si se hace referencia a un campo
de bit por medio de un puntero, se conveniente utilizar el operador flecha
"->".
Las variables de campos de bits tienen ciertas restricciones:
- No puede cogerse la direccin de una variable de campo de bit.
- No pueden hacerse tablas de una variable de campo de bit.
- De mquina a mquina no se puede saber si los campos van de derecha
a izquierda o viceversa.
Esto implica que cualquier cdigo que utilice campos de bit puede tener
dependencia con la mquina (disminuye la transportabilidad).
Es vlido mezclar elementos de una estructura normal con elementos de
campos de bit. Por ejemplo:
struct emp
{
struct addr direc ;
float paga ;
unsigned activo : 1 ; /* activo -1- inactivo -0- */
unsigned horas : 1 ; /* salario por horas S/N o 1/0 */
unsigned deducciones: 3 ;
} ;
Esta estructura define el registro de un empleado; utiliza solamente 1
byte para mantener tres partes de informacin: el esto del empleado, si el
empleado es asalariado y el nmero de deducciones. Si se utilizase el campo
de bit, esta informacin utilizara 3 bytes.
5.7 UNIONES
Una union es una variable que puede contener (en momentos diferentes)
objetos de diferentes tipos y tamaos, y el compilador hace el seguimiento del
tamao y requisitos de alineacin. La sintaxis de union es similar a la de una
estructura, como se demuestra a continuacin:
union u_tipo
{
int entero ;
char carac ;
} ;
Como con una declaracin de estructura, esta definicin no declara
ninguna variable. Se puede declarar una variable poniendo su nombre al final
de la definicin o usando una sentencia separada de declaracin. Para declarar
una variable union cnvt de tipo u-tipo usando la definicin se escribira:
Cap.5-15
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
union u_tipo cnvt ;
Ilustr. 3 Cmo entero y carac utilizan la unin cnvt.
En cnvt, tanto el entero entero y el carcter carac comparten la misma
posicin de memoria. (Sin embargo, el primero ocupa dos bytes y el segundo
uno). En la figura tres se muestra la forma en que entero y carac comparten
la misma direccin.
Otro ejemplo podra ser:
union u_var
{
int int_var ;
float float_var ;
char *dir_var ;
}
u ;
La variable u ser suficientemente grande como para mantener al mayor
de los tres tipos: el tamao especfico depende de la implementacin
(ordenador donde se compile). Cualquiera de estos tipos puede ser asignado a
u y despus empleado en expresiones, mientras que el uso sea consistente: el
tipo recuperado debe ser el tipo que se almacen ms recientemente. Es
responsabilidad del programador llevar el control del tipo que est
almacenando actualmente en una union; si algo se almacena como un tipo y se
recupera como otro, el resultado depende de la implantacin.
Sintcticamente, se tiene acceso a los miembros de una union con los
operadores punto "." y flecha "->" (a travs de un puntero) como se indica:
nombre_unin.miembro
puntero_unin->miembro
Precisamente como a las estructuras. Si la variable u_var se emplea para
llevar el registro del tipo actualmente en u, entonces se podra ver el cdigo
como:
Cap.5-16
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
if(u_var == INT)
printf("%d\n", u.int_var) ;
else if(u_var == FLOAT)
printf("%f\n", u.float_var) ;
else if(u_var == STRING)
printf("%s\n", u.dir_var) ;
else
printf("dato incorrecto %d en u_var\n", u_var) ;
Las uniones pueden presentarse dentro de estructuras y tablas, y
viceversa. La notacin para tener acceso a un miembro de una union en una
estructura (o viceversa) es idntica a la de las estructuras anidadas. Por
ejemplo, en la tabla de estructuras definido por tabla_sim[]:
struct
{
char *nombre ;
int flags ;
int u_var ;
union
{
int i_var ;
float f_var ;
char *dir_var ;
}
u ;
}
tabla_sim[num_datos] ;
Al miembro i_val se le refiere como:
tabla_sim[INDICE].u.i_val
Y al primer carcter de la cadena dir_val por cualquiera de estos:
*tabla_sim[INDICE].u.dir_val
tabla_sim[INDICE].u.dir_val[0]
En efecto, una union es una estructura en la cual todos los miembros
tienen un desplazamiento de cero a partir de la base, la estructura es
suficientemente grande para mantener al miembro "ms ancho", y la alineacin
es la apropiada para todos los tipos de la union. Estn permitidas las mismas
operaciones sobre las uniones como sobre las estructuras: asignacin o copia
como unidad, tomar la direccin, y hacer el acceso a un miembro.
Una union slo se puede inicializar con un valor del tipo de su primer
miembro, as que la union u descrita anteriormente slo se puede inicializar
con un valor entero.
Un programa interesante que combina uniones con campos de bit visualiza
el cdigo ASCII en binario que se genera cuando se pulsa una tecla en la
consola. La union permite a getche() asignar el valor de la tecla pulsada a
una variable de carcter, mientras el programa usa el campo de bits para
visualizar los bits individuales. Estudiar este programa para asegurarse que
se entiende completamente su funcionamiento.
Cap.5-17
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
#include <stdio.h> /* cdigo ASCII en binario para caracteres */
#include <conio.h>
struct byte /* un campo de bit se decodificar */
{
unsigned a: 1; unsigned b: 1; unsigned c: 1; unsigned d: 1;
unsigned e: 1; unsigned f: 1; unsigned g: 1; unsigned h: 1;
};
union bits
{
char ch ;
struct byte bit ;
} ascii ;
void deco(union bits b) ; /* prototipo de funcin */
main()
{
do
{
ascii.ch = getche() ;
printf(" : ") ;
deco(ascii) ;
}
while(ascii.ch != q) ; /* "quit" si se teclea q */
}
void deco(union bits b) /* visu. el patrn de bit por carcter */
{
printf("%d ",(b.bit.h) ? 1 : 0) ;
printf("%d ",(b.bit.g) ? 1 : 0) ;
printf("%d ",(b.bit.f) ? 1 : 0) ;
printf("%d ",(b.bit.e) ? 1 : 0) ;
printf("%d ",(b.bit.d) ? 1 : 0) ;
printf("%d ",(b.bit.c) ? 1 : 0) ;
printf("%d ",(b.bit.b) ? 1 : 0) ;
printf("%d\n",(b.bit.a) ? 1 : 0) ;
}
La unin puede proporcionar tambin un medio de cargar un entero en un
campo de bit. Como se puede ver en el apartado anterior. C permite asignar un
puntero directamente a una estructura de campo de bit. Sin embargo, este
programa crea una union que contiene un entero y un campo de bit.
Cuando biosequip() devuelve la lista del equipo codificado como entero,
se le asigna un entero. Sin embargo, el programa es libre de utilizar el campo
de bit cuando se emitan los resultados:
/* Presenta el nmero de disquetes y puertos serie */
#include <bios.h> /* fichero de cabecera para biosequip() */
#include <stdio.h>
main()
{
struct equip
{
unsigned floppy_boot : 1 ;
unsigned copro8087 : 1 ;
unsigned ram_madre : 2 ;
unsigned modo_video : 2 ;
unsigned disqueteras : 2 ;
unsigned dma : 1 ;
unsigned puertos_serie : 3 ;
Cap.5-18
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
unsigned adapta_juegos : 1 ;
unsigned : 1 ;
unsigned num_impresoras : 2 ;
} ;
union
{
struct equip eq ;
unsigned i ;
}
eq_union ;
eq_union.i = biosequip() ;
printf("%d discos flexibles\n",eq_union.eq.disqueteras+1) ;
printf("%d puertos serie \n",eq_union.eq.puertos_serie) ;
}
5.8 TIPO ENUMERADO (ENUMERACIONES)
Una enumeracin es un conjunto de constantes enteras con nombre que
especifican todos los valores legales que puede tomar una variable del tipo
indicado. Las enumeraciones no son raras en la vida corriente. Por ejemplo,
una enumeracin de los das de la semana es:
.- lunes, martes, mircoles, jueves, viernes, sbado y domingo
Se definen enumeraciones de igual manera que estructuras, con la palabra
clave enum para el comienzo de un tipo de enumeracin. El formato general es:
enum nombre_tipo_enum { lista de enumeracin } lista_de_variables ;
Aqu la lista de enumeracin es una lista de nombre separados por comas,
representando los valores que una variable de tipo enumeracin puede tener.
Son opcionales nombre_tipo_enum y la lista_de_variables. Como las
estructuras, se usa el nombre del tipo de enumeracin para declarar las
variables de ese tipo. El siguiente fragmente define una enumeracin llamada
semana que declara DIA para poseer el mismo tipo.
enum semana { lunes,martes,miercoles,jueves,viernes,sabado,domingo} ;
enum semana DIA ;
Dada la definicin y declaracin son vlidas las siguientes sentencias:
DIA = jueves ;
if(DIA == lunes)
printf("hoy es lunes\n") ;
La regla principal de una enumeracin es que cada smbolo establezca un
valor ENTERO. Por tanto, se pueden usar los smbolos en cualquier expresin
entera. A menos que se inicialice de otra manera, el valor del primer smbolo
de la enumeracin es 0, el valor del segundo smbolo es 1 y as sucesivamente.
Por tanto:
printf("%d %d", lunes, miercoles) ;
Visualiza "0 2" en la pantalla.
Se puede especificar el valor de uno o ms smbolos usando un
inicializador. Para hacerlo, poner un signo igual "=" y un valor ENTERO
Cap.5-19
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
despus del smbolo. Cuando se indica el valor inicial de uno de los smbolo
(enumerador) el ordenador asigna valores ms grandes (en una unidad) que el
valor inicial anterior a los smbolos que aparecen despus de aquel elemento
inicializado. Por ejemplo, lo siguiente asigna el valor 100 a jueves:
enum semana {lunes,martes,miercoles,jueves=100,viernes,sabado,domingo};
Los valores de estos smbolos son los siguientes:
lunes 0
martes 1
mircoles 2
jueves 100
viernes 101
sbado 102
domingo 103
Una suposicin comn, pero errnea, es que los smbolos se puede
introducir y sacar directamente. Sin embargo, ste no es el caso. Por ejemplo,
el siguiente fragmento de cdigo no funciona como se quiere:
/* esto no funcionar */
DIA = domingo ;
printf("%s", DIA) ;
Recordar que el smbolo domingo es simplemente un identificador
(etiqueta) para un entero; no es una cadena. Por esta misma razn, no se puede
usar este cdigo para conseguir el resultado deseado:
/* este cdigo es errneo */
get(s) ;
strcpy(viernes, s) ;
As este cdigo no provoca que el ordenador convierta un carcter que
contiene el nombre de un smbolo automticamente como un smbolo.
Realmente, el cdigo de creacin para introducir y presentar una
enumeracin de smbolos es muy tedioso (a menos que se desee para valores
enteros). Por ejemplo, se necesita usar este cdigo para visualizar, en
palabras, el da de la semana que contiene DIA.
switch(DIA)
{
case lunes:
printf("lunes") ;
break ;
case martes:
printf("martes") ;
break ;
case miercoles:
printf("mircoles") ;
break ;
case jueves :
printf("jueves") ;
break ;
case viernes :
printf("viernes") ;
break ;
case sabado :
printf("sbado") ;
break ;
Cap.5-20
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
case domingo :
printf("domingo") ;
break ;
}
A veces, para traducir un valor de la enumeracin en su cadena
correspondiente, se puede declarar una tabla de cadenas de caracteres que se
usan para valores de numeracin como un ndice. Por ejemplo, este fragmento
de cdigo tambin sacar la cadena apropiada:
char DIA_semana[][20] =
{
"lunes" ,
"martes" ,
"mircoles" ,
"jueves" ,
"viernes" ,
"sbado" ,
"domingo" ,
}
...
printf("%s", DIA_semana[DIA]) ;
Este fragmento slo funcionar si no se usa la inicializacin de
smbolos porque se debe indexar la tabla de cadena a 0.
Dando por hecho que se debe convertir los valores de la enumeracin
manualmente a sus cadenas de valores para la E/S por consola, se puede
encontrar el uso ms importante de los valores de enumeracin en las rutinas
que no hacen tales conversiones. Por ejemplo, es comn ver las enumeraciones
usadas para definir una tabla de smbolo del compilador.
5.9 PALABRAS CLAVE -typedef- Y -sizeof-
A continuacin se estudian las palabras reservadas typedef y sizeof.
TYPEDEF
C proporciona una facilidad llamada typedef para crear nuevos tipos de
datos. Realmente no se est creando una nueva clase de datos, sino definiendo
un nuevo nombre para un tipo existente. Este proceso puede ayudar a hacer
programas dependientes de la mquina ms portables; slo se necesita cambiar
las sentencias typedef. El proceso tambin puede ayudar a documentar el cdigo
permitiendo nombre descriptivos para los tipos de datos estndar. El formato
general de la sentencia typedef es:
typedef tipo nuevo_nombre ;
Donde tipo es cualquier tipo de dato y nuevo_nombre es el nombre para
ese tipo. Por ejemplo la sentencia:
typedef int longitud ;
Hace del nombre longitud un sinnimo de int. El tipo longitud puede
emplearse en declaraciones, casts, etc., exactamente de la misma manera en que
lo podra ser int :
longitud lon, max_lon ;
longitud *tamao[] ;
Cap.5-21
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
De modo semejante, la declaracin:
typedef char * cadena ;
Convierte a cadena en un sinnimo para char * o puntero a carcter, que
despus puede usarse en declaraciones y casts:
cadena dir_p, lInea_puntero[MAX_TAM], toma_mem(int) ;
int strcmp(cadena, cadena) ;
p = (cadena) malloc(100) ;
Se puede usar typedef para crear nombres para tipos ms complejos,
todava, como se muestra aqu:
typedef struct clientes
{
float due ;
int over_due ;
char nombre[40] ;
} ;
clientes clist[NUM_CLIENTES]; /* define la tabla de estructuras
de tipo clientes. */
Usando typedef puede ayudar a que el cdigo sea ms legible y fcil de
portar a una mquina nueva. Sin embargo, recordar que no se est creando
ningn tipo de datos nuevo.
SIZEOF
Hasta aqu se ha visto que se pueden usar estructuras, union y
enumeraciones para crear variables de tamao variable y que el tamao real de
estas variables puede cambiar de mquina a mquina. Se puede usar el operador
monario sizeof para calcular el tamao de cualquier tipo o variable y ayudar
a eliminar de los programas el cdigo dependiente de la mquina.
Por ejemplo, en un IBM PC tiene los siguientes tamaos para los tipos
de datos:
Tipo Tamao (bytes)
char 1
int 2
long int 4
float 4
double 8
Por tanto, este cdigo imprimir los nmeros 1, 2 y 8 en la pantalla:
char ch ;
int i ;
double d ;
printf("%d", sizeof(ch)) ;
printf("%d", sizeof(i)) ;
printf("%d", sizeof(d)) ;
Cap.5-22
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
El operador sizeof es un operador en tiempo de compilacin: el ordenador
sabe toda la informacin necesaria para calcular el tamao de cualquier
variable en tiempo de compilacin. Por ejemplo, considerar este cdigo:
union x
{
char ch ;
int i ;
float f ;
}varios ;
El sizeof(varios) ser 4. En tiempo de ejecucin, no importa que la
union varios est guardada realmente; el problema principal es el tamao de
la variable ms grande que puede guardar porque la union debe ser tan grande
como el elemento mayor.
5.10 MODIFICADORES DE ACCESO -const- Y -volatile-
El lenguaje C tiene dos modificadores de tipo que se usan para controlar
la forma en que se pueden acceder o modificar las variables. Estos
modificadores se llaman const y volatile.
MODIFICADOR CONST
Las variables de tipo const no pueden cambiarse por el programa durante
la ejecucin, excepto darle un valor inicial. Por ejemplo:
const float versiOn = 6.22 ;
Crear un float llamado versiOn que NO PUEDE modificarse por el
programa. Puede, sin embargo, usarse en otros tipos de expresiones. Una
variable const recibir su valor desde una inicializacin explcita o por
medio de alguna dependencia del HARDWARE. Aplicando un modificador const a una
declaracin de variable asegura que otras partes del programa no modificarn
la variable.
Las variables const tienen un uso importante: pueden proteger los
argumentos de una funcin de que los modifique esa funcin. Cuando el programa
pasa un puntero a una funcin, es posible que la funcin modifique la variable
real a la que apunta. Sin embargo, si se especifica este puntero como const
en la declaracin de parmetro, el cdigo de la funcin no podr modificar lo
que apunta. Por ejemplo, la funcin code() del prximo programa desplazar
cada letra del mensaje una vez. Por tanto, una A ser B y as sucesivamente.
Usando const en la declaracin del parmetro obliga a que el cdigo dentro de
la funcin no pueda modificar el objeto al que apunta el parmetro.
#include <stdio.h>
void code(const char *str) ;
main()
{
code("esto es una prueba") ;
}
void code(const char *str)
{
while(*str)
printf("%c", (*str++) + 1) ;
}
Cap.5-23
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
Si, por alguna razn, se ha escrito code() de alguna manera que el
argumento pudiera modificarse, no se podra compilar. Por ejemplo, si se
escribe code() como esto:
void code(const char *str) /* es errneo */
{
while(*str)
{
*str = *str + 1 ;
printf("%c", *str++) ;
}
}
Se vera el siguiente mensaje del compilador:
Cannot modify a const object in function code
La segunda utilizacin de const es proporcionar la verificacin de que
el programa, en realidad, modifica una variable. Recordar: una variable de
tipo const se puede modificar por cualquier cosa que se encuentre fuera del
programa.
Por ejemplo, un dispositivo de hardware puede establecer su valor
propio. Sin embargo, el declarar una variable del tipo const, puede probar que
cualquier cambio en esta variable se ha producido por motivos externos.
MODIFICADOR VOLATILE
Se usa el modificador volatile para decirle al compilador que se puede
cambiar un valor de variable de forma no explcita por el programa. Por
ejemplo, una direccin de una variable global se puede pasar a la rutina de
reloj del sistema operativo y se usa para guardar la hora real del sistema.
En esta situacin, los contenidos de la variable se alteran sin ninguna
sentencia de asignacin explcita en el programa. Esto es importante porque
algunos compiladores optimizarn automticamente ciertas expresiones
suponiendo que los contenidos de una variable no cambian en la expresin si
no lo hace el lado izquierdo de la sentencia de asignacin. Por ejemplo,
suponer que el mecanismo de reloj de el ordenador actualiza el reloj cada
dcima de segundo. Si no se declara reloj como volatile, entonces las
sentencias siguientes podran no funcionar correctamente:
int reloj, timer ;
...
timer = reloj ; /* se referencia reloj */
/* hacer algo */
printf("el tiempo de retardo es %d", reloj - timer ) ;
Como el programa no altera reloj y no est declarada como volatile, el
compilador es libre de optimizar el cdigo de forma que el valor de reloj se
reexamina (para mantener el valor de la asignacin) en la sentencia printf().
Sin embargo, declarando reloj como:
volatile int reloj ;
Asegura que no tendr lugar ninguna optimizacin y el compilador
examinar el valor de reloj cada vez que se referencia.
Aunque se vea extrao al principio, se pueden usar const y volatile
juntos. Por ejemplo, suponer que 0x30 es el valor de una porcin que se cambia
slo por condiciones externas. Entonces con la declaracin siguiente sera
precisamente lo que se pretende prevenir es la posibilidad de efectos
laterales (modificacin dentro del programa y no por accin externa)
Cap.5-24
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
accidentales.
const volatile unsigned char *puerto = 0x30 ; /* fuerza a modificacin
nicamente externa */
5.11 ESPECIFICADORES DE CLASE DE ALMACENAMIENTO
La clase de almacenamiento determina el tiempo de vida de almacenamiento
asociado con el objeto identificado. El lenguaje C soporta cuatro
especificadores de almacenamiento. Son:
- auto
- extern
- static
- register
Estos especificadores le dicen al compilador cmo almacenar la variable
que sigue. El especificador de almacenamiento precede a la declaracin de
variable. El formato general es:
especificador_almacenamiento especificador_tipo lista_de_variables ;
A continuacin se examinar cada especificador.
ESPECIFICADOR -auto-
Se usa el especificador auto para declarar variables locales. Sin
embargo, se usa raramente porque sera redundante dado que las variables
locales son auto por defecto. Es muy raro que se utilice esta palabra clave
en un programa.
Recordar que tienen un alcance local al bloque o funcin y existen en
memoria mientras se ejecuta la unidad de cdigo que la contiene.
ESPECIFICADOR -extern-
Los programas con los que se han trabajado han sido pequeos, tan
pequeos de hecho, que muchos no superan las 25 lneas en la pantalla. Sin
embargo, en la prctica real, los programas tienden a ser muy grandes. Incluso
aunque el compilador sea extremadamente rpido, como el archivo crece, el
tiempo de compilacin llega a ser aburrido. Cuando esto sucede, se debera
fraccionar el programa en dos o ms archivos separados. El lenguaje C contiene
el especificador extern, que ayuda a soportar la aproximacin de archivos
mltiples. De este modo, pequeos cambios en un archivo no necesita que se
vuelva a compilar todo el programa (es un ahorro sustancial de tiempo en
proyectos grandes).
Como C permite enlazar mdulos de un programa grande compilados
separadamente para acelerar la compilacin y ayudar el manejo de grandes
proyectos, deber existir alguna forma de decir a todos los archivos las
variables globales que requiere el programa. Solamente se puede tener una
copia de cada variable global por programa. Si se intentan declarar dos
variables globales con el mismo nombre dentro del mismo archivo, el compilador
selecciona una y la utiliza. Aparece un problema si simplemente declara en
cada archivo todas las variables globales que se necesitan. Aunque el
compilador no emite ningn mensaje de error en tiempo de compilacin,
realmente se est tratando de crear dos (o ms) copias de cada variable. Los
problemas empiezan cuando se trata de enlazar los mdulos juntos. El
"linkador" (reubicador) visualizar mensajes de aviso porque no sabr qu
variables usar.
Cap.5-25
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
La solucin es declara todas las variables globales en un archivo y usar
declaraciones extern en los otros, como se muestra en este ejemplo:
Archivo 1 Archivo 2
int x, y ; extern int x, y ;
char ch ; extern char ch ;
main() {.......} func22() { x = y/10 ; }
fun1() { x = 123 ; } func23() { y = 10 ; }
En el archivo 2, el cdigo copia la lista de variables globales desde
el archivo uno y aade el especificador extern a la declaracin. El
especificador extern la dice al compilador que el tipo y nombre de variables
han sido declaras en otro lugar. Dicho de otro modo, extern le permite al
compilador saber cules son los tipos y nombre para estas variables globales
sin realmente crear almacenamiento para ellas. Cuando el "linkador" enlaza dos
mdulos juntos, resuelve todas las referencias a las variables externas.
Cuando se usa una variable global dentro de una funcin que est en el
mismo archivo que la declaracin para la variable global, opcionalmente se
puede usar extern. Por ejemplo:
int first, last ; /* definicin global */
main()
{
extern int first ; /* uso opcional de la declaracin extern */
}
Aunque las declaraciones de variables extern puedan darse en el mismo
archivo de la declaracin global, no es necesario. Si el compilador encuentra
una variable que no ha sido declarada, ver si encaja con alguna de las
variables globales. Si es as, C asumir que es la variable que est siendo
referenciada.
VARIABLES CON MODIFICADOR -static-
Las variables static son variables permanentes en su propia funcin o
archivo. Se diferencian de las variables globales porque son desconocidas
fuera de sus funciones o archivo, pero mantienen sus valores entre llamadas.
Esta caracterstica puede hacer utilsimas las variables static cuando se
escriben funciones generalizadas y bibliotecas de funciones que pueden usar
otros programadores. Como static tiene efectos diferentes cuando se usa con
variables locales que con globales, a continuacin se examinan separadamente.
VARIABLES -static- LOCALES
Cuando se aplica el modificador static a una variable local, provoca que
el compilador cree un almacenamiento permanente para la variable local de la
misma manera que una variable global. La clave que diferencia una variable
static local y una global es que la primera es conocida slo en el bloque en
que se declara. En trminos sencillos, una variable static local es una
variable local que retiene su valor entre llamadas de funcin.
Es importante para la creacin de funciones aisladas que las variables
static locales estn disponibles porque hay varios tipos de rutinas que deben
preservar un valor entre llamadas. Si un lenguaje de programacin no
permitiera variables static, entonces tendra que utilizar variables globales
- que abrira la puerta a efectos laterales -. Un buen ejemplo de una funcin
que requerira tales variables es un generador de series de nmeros que
produce un nmero nuevo basado en el ltimo. Se podra declarar una variable
global para mantener el valor generado. Mientras que se podra declarar una
variable global para este valor, cualquier programa que use la funcin tendra
que declarar esa variable, asegurndose que no entran en conflicto con
Cap.5-26
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
cualquier otra variable ya declarada; este requerimiento tendra un mayor
rendimiento. Tambin, usando una variable global hara difcil poner esta
funcin en una biblioteca de funciones. La mejor solucin es declarar la
variable que guarda el nmero generado como static, como se muestra en este
programa.
#include <stdio.h>
int series(void) ; /* prototipo de funcin */
main()
{
int i ;
for(i = 0 , i < 10 ; i++)
printf"%d", serie()) ;
}
series(void)
{
static int series_num ;
series_num = series_num + 23 ;
return(series_num) ;
}
En este fragmento, la variable series_num contina existiendo entre
llamadas de funciones, en vez de ir y venir la manera que una variable local
lo hara. As cada llamada a series() puede producir un nuevo miembro de la
serie que se basa en el nmero anterior, sin declarar series_num globalmente.
La funcin nunca da un valor inicial a la variable esttica series_num.
Esto significa que el valor inicial ser cero por que el compilador inicializa
las variables static locales a cero. Mientras que este paso de
inicializaciones es aceptable para algunas aplicaciones, la mayora de los
generadores de series necesitarn un punto de arranque bien definido. Para
hacer esto requiere inicializar series_num antes de la primera llamada a
series(), que se puede hacer fcilmente slo si series_num es una variable
global. Sin embargo, para evitar la necesidad de hacer global series_num
(motivo principal de hacerla static) debe estudiarse el segundo uso de static.
VARIABLES -static- GLOBALES
Cuando se aplica el especificador static a una variable global, static
instruye al compilador para crear una variable global que se conozca slo por
el archivo en el que se declara la variable static global. As, incluso aunque
la variable es global, otras rutinas en otros archivos pueden no tener
conocimiento de ella y modificar el contenido directamente; no est sujeta a
efectos laterales. Por tanto, para las pocas situaciones donde una variable
static local no puede hacer el trabajo, se puede crear un pequeo archivo que
contiene slo las funciones que necesitan la variable static global,
separadamente compilar ese archivo, y usarlo sin preocuparse de los
mencionados efectos laterales.
Para ver cmo se puede usar un bucle static global, el generador de
series de nmeros dado antes ha sido recodificado de manera que usa un valor
inicial para comenzar las serie a travs de una llamada a una segunda funcin
denominada comienza_serie(). El archivo completo que contiene series(),
comienza_serie() y series_num se muestra ahora:
/* todo contenido en un mismo archivo */
Cap.5-27
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
static int series_num ;
series()
{
series_num = series_num + 23 ;
return(series_num) ;
}
comienza_serie() /* inicializa series_num */
{
series_num = valor_inicial ;
}
Llamando a comienza_serie() con un valor entero conocido inicializa el
generador de series. Despus de esto, llama a series() que generar el
siguiente elemento en la serie.
Recordar que los nombres de las variables static locales son conocidas
slo por la funcin o bloque de cdigo en la que se declaran y los nombres de
variables static globales son conocidas slo en el archivo en el que residen.
Si se ponen las funciones series() y comienza_series() en una biblioteca, se
puede usar las funciones pero no referenciar la variable series_num. Se oculta
el resto del cdigo en el programa. Se pueden declara y usar incluso otra
variable llamada series_num en el programa (en otro archivo) y no confundirse.
En esencia, el modificador static permite la existencia de variables que se
conocen en las funciones que las necesitan, sin confundir con otras funciones,
de este modo controla y limita la posibilidad de efectos secundarios.
Las variables static permiten al programador ocultar porciones del
programa de otras partes. Esta posibilidad puede tener una ventaja tremenda
cuando se trata de manejar un programa grande y complejo.
ESPECIFICADOR -register-
Otro modificador de tipo importante en C es el llamado register y se
aplica slo a variables locales de caracteres y enteros. Provoca que el
compilador trate de mantener ese valor en un registro de la CPU, en vez de
ponerlo en memoria, donde se guardan las variables normales. Las operaciones
sobre variables register puede ocurrir ms rpido que sobre las variables en
memoria porque no son necesarios accesos a memoria (elemento de almacenamiento
que trabaja a una velocidad muy inferior a la del procesador) para determinar
o modificar sus valores. Este tiempo ahorrado hace ideal el uso de las
variables register para bucles de control. Slo se puede aplicar el
especificador register a variables locales y a parmetros formales en una
funcin. Aqu no se puede usar las variables register globales.
Este es un ejemplo de la forma de declarar una variable register de tipo
int y la forma de usarla para controlar un bucle. Esta funcin calcula el
resultado de m
e
para enteros:
int_pwr(int m, register int e)
{
register int temp ;
temp = 1 ;
for(;e ;e--)
temp = temp * m ;
return temp ;
}
En este ejemplo, el cdigo declara e y temp como variables register
porque las usa en el bucle. En la prctica general, se usaran variables
Cap.5-28
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
register en uso para cualquier momento.
Dado el tipo de procesador y la implementacin especfica de C que se
usa, determina el nmero exacto de variables registro que se permiten en
cualquier funcin. Para Turbo C, pueden ser dos variables register a la vez.
El usuario no debe preocuparse de declarar demasiadas variables register
porque automticamente este compilador convertir variables register en no
registro cuando el nmero alcance el lmite (para permitir la portabilidad de
cdigo a distintos procesadores).
Para ver la diferencia que cualquier variable registro puede tener, el
programa siguiente mide el tiempo de ejecucin de dos bucles for que difieren
slo en el tipo de variable que controlan. Este programa usa la funcin time()
(time.h) que se encuentra en la biblioteca estndar de C.
/* diferencia entre variable normal y registro */
#include <stdio.h>
#include <time.h>
unsigned int i ; /* no registro */
unsigned int delay ;
main()
{
register unsigned int j ;
long t ;
t = time(\0) ;
for(delay = 0 ; delay < 100 ; delay ++)
for(i = 0; i < 64000 ; i++) ;
printf("t. para bucle no registro: %1d\n", time(\0) - t) ;
t = time(\0) ;
for(delay = 0 ; delay < 100 ; delay ++)
for(j = 0; j < 64000 ; j++) ;
printf("t. para bucle registro: %1d\n", time(\0) - t) ;
}
Si se ejecuta este programa, se encontrar que el controlador register
de bucle se ejecuta alrededor de la mitad de tiempo que el invertido por el
controlado sin modificador de registro.
Un ltimo punto: el compilador de Turbo C convierte automticamente los
dos primeros caracteres o enteros locales en tipo register como optimizacin
(si est configurado para ello).
Cap.5-29
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
EJERCICIOS PROPUESTOS, CAPTULO 5
1) En trminos generales qu es una estructura y una unin?. Indicar
la forma de crear una estructura llamada tipo_es que contenga estos 5
elementos:
char tipo_char ;
float tipo_float ;
int tipo_int ;
char tipo_cad[80];
double tipo_double ;
2) Qu es incorrecto en este fragmento?. Razona tu respuesta.
struct tipo_s
{
int i ;
long l ;
char cadena[80] ;
}
s ;
...
i = 10 ;
...
3) Es posible nombrar a elementos de estructuras de igual forma que
otras variables?, demostrarlo con un ejemplo.
4) Escribir un programa que utilice una tabla de estructuras para
guardar los cuadrados y cubos de los nmeros del 1 al 10. Al finalizar la
carga, deben mostrarse los contenidos de la tabla.
5) Escribir un programa que utilice una tabla de estructuras para
almacenar nombres de libros, autor y unidades. Limitar la base de datos a 5
libros. Una vez finalizada la introduccin de informacin, presentarla en
pantalla.
6) Escribir un programa que contenga dos variables de estructura
definidas como:
struct tipo_str
{
int i ;
char ch ;
double d ;
}var1, var2 ;
El programa debe asignar valores iniciales a todos los elementos de
ambas estructuras (asegurndose que los valores de dichos elementos sean
diferentes). Debe usar una funcin llamada str_interc() para que el programa
intercambie el contenido de var1 y var2.
7) En C, como ya se conoce, no se puede pasar una tabla a una funcin
como un parmetro. Sin embargo, hay un modo de evitar esta restriccin. Si
se incluye la tabla dentro de una estructura, el array se pasa utilizando
el convenido de llamada por valor estndar.
Escribir un programa que lo demuestre pasando una cadena de una
estructura a una funcin, alterando su contenido dentro de la funcin y
demostrando que la cadena original no se modifica despus de volver de la
funcin.
Cap.5-30
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
8) Qu es un campo de bits?.
9) Qu est mal en este fragmento?, porqu?.
struct tipo str
{
int a ;
char b ;
}
dosvar, * dir_var ;
...
dir_var = & dosvar ;
dir_var.a = 10 ;
...
10) Es necesario dar nombre a cada bit cuando se usa campos de bits?.
demostrarlo con un ejemplo.
11) Escribir un programa que cree una estructura que contenga tres
campos de bits llamados a, b y c. Los campos a y b deben tener un tamao de
tres bits y c de 2 bits. Deben declararse las variables como signed. A
continuacin asignar a cada una un valor y posteriormente mostrar los
resultados.
12) Utilizando la estructura de campos de bits, realizar un programa
que presente en pantalla el estado de los bits pertenecientes a los registros
del puerto paralelo:
Registro de datos
Registro de control
Registro de estado
Para cada registro debe de ofrecerse informacin del significado de
cada uno de los bits.
13) Escribir un programa que utilice una unin para convertir un -int-
en un -long- y demuestre que esto funciona.
14) Escribir un programa que use una union para mostrar como un
carcter los bytes individuales que forman un entero introducido por usuario.
15) Qu muestra este fragmento? (suponer enteros de 2 bytes y doubles
de 8 bytes).
...
union
{
int i ;
double d ;
}
uvar ;
printf("-%d-", sizeof uvar) ;
...
16) Suponer que un compilador realmente slo optimiza el tiempo de
acceso de acceso de dos variables register por funcin. En este programa, qu
dos variables son las mejores para convertirlas en variables register?. Razona
tu respuesta.
Cap.5-31
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
#include <stdio.h>
#include <conio.h>
main()
{
int i, j, k, m ;
do
{
printf("Introduzca un valor: ") ;
scanf("%d", &i) ;
m = 0 ;
for(j = 0; j < i ; j++)
for(k = 0 ; k<100 ; k++)
m = k + m ;
}
while(i > 0) ;
}
17) Escribir un programa que contenga una funcin llamada sum_it() que
tiene el prototipo:
void sum_i(int valor) ;
Hacer que esta funcin use una variable entera static local para
mantener y mostrar un resultado total de los valores de los parmetros
con los que se ha llamado. Por ejemplo, si sum_it() se llama tres veces
con los valores 3, 6, 4 entonces sum_it() mostrar 3, 9 y 13.
18) Qu est mal en este fragmento?
register int i ;
int *p ;
p = &i ;
19) Dnde puede utilizarse la palabra reservada const?. Utilizar un
ejemplo para demostrarlo.
20) Una variable declarada como const, podra formar parte de una
sentencia de asignacin?. Razona tu respuesta.
21) Un buen momento para usar const es cuando se quiere introducir un
nmero de control de versin en un programa. El uso de una variable const para
guardar la versin, la protege de cambios accidentales. Escribir un programa
que ilustre cmo se puede hacer esto. Usar el nmero de versin 9.99.
22) Por s mismo, intentar desarrollar un ejercicio donde se utilice
volatile.
23) Crear una enumeracin de los meses del ao.
24) Es correcto este fragmento?. Razona tu respuesta.
enum coches {seat, fiat, for} marca ;
...
marca = for ;
Cap.5-32
Captulo 5: Tipos de datos.- Definidos por usuario y Avanzados ProgramacinenC
printf("el coche es %s", marca) ;
...
25) Realizar un programa que muestre cmo hacer que UL sea el nuevo
nombre para unsigned long. Comprobar que funciona escribiendo un pequeo
programa que declare una variable usando UL, le asigne un valor y muestre el
valor en la pantalla.
26) Qu est mal en este fragmento?.
typedef balance float ;
27) Teniendo definida la estructura del siguiente programa:
#include <stdio.h>
struct tipo_s
{
int i ;
char ch ;
int *p ;
double d ;
}
s ;
main()
{
printf("tipo_s = %d", sizeof(struct tipo_s)) ;
}
Qu imprimir en pantalla?.
Cap.5-33
CAPTULO 6
Profundizacin en las Funciones
Captulo 6: Profundizacin en las Funciones ProgramacinenC
6.1 INTRODUCCIN
En C las funciones son bloques construidos en los que discurre todo el
programa de actividades. En el captulo 1, se han utilizado funciones de una
manera ms o menos intuitiva. En este captulo se estudiarn en detalle,
aprendiendo cosas tales como el mbitos de las reglas que gobiernan funciones
(y variables), la utilizacin de punteros relacionados con funciones, los
modificadores de tipo de funcin, la forma de crear funciones recursivas,
propiedades especiales de la funcin main(), y caractersticas importantes de
la programacin modular (uso de varios ficheros en un programa).
6.2 REGLAS DE MBITO DE LAS FUNCIONES
Las reglas de mbito de un lenguaje gobiernan si una parte de cdigo
"sabe" o no, o accede o no a otra parte de cdigo o datos. El captulo primero
toca ligeramente este asunto; ahora esta seccin lo examina ms estrechamente.
En C, cada funcin es un bloque discreto de cdigo. Un cdigo de funcin
es exclusivo de la funcin y no es accesible a ninguna sentencia en otra
funcin excepto a travs de otra funcin. (por ejemplo, no se puede usar goto
para saltar en medio de otra funcin). El cdigo que comprende el cuerpo de
la funcin est oculto del resto del programa y - a menos que el cdigo use
variables o datos globales - no puede afectar ni ser afectado por otras partes
de programa.
Otra forma de expresar lo anteriormente comentado: el cdigo y los datos
que se definen en una funcin no interactan con el cdigo o los datos que se
definen en otra funcin, porque las dos funciones tiene un mbito (entorno de
trabajo) diferente.
Hay tres tipos de variables:
- Variables locales
- Parmetros formales
- Variables globales
Las reglas de mbito gobiernan cmo pueden acceder otras partes del
programa a estos tipos y establece el tiempo de "vida" de las variables.
VARIABLES LOCALES
Como se sabe, las variables que se declaran dentro de una funcin se
llaman variables locales. Sin embargo, C soporta un concepto ms amplio de
variable local que el visto anteriormente. Una variable se puede declarar en
un bloque de cdigo y ser local a ese bloque. En realidad, las variables que
son locales a una funcin son simplemente un caso especial del concepto
general.
Las variables locales pueden ser referenciadas por las sentencias que
estn dentro del bloque en el que dichas variables se declaran. As las
variables locales no son conocidas fuera de su propio bloque de cdigo y su
mbito est limitado al bloque en el que se declaran. Recordar que un bloque
de cdigo empieza cuando el ordenador encuentra una llave.
Uno de los aspectos ms importantes de las variables locales que se
debera entender es que existen slo durante la ejecucin del bloque de cdigo
en el que se declaran; es decir, una variable local se crea al entrar en su
bloque y se destruye despus de salir.
Una funcin es el bloque de cdigo ms comn en el que se declaran
Cap.6-2
Captulo 6: Profundizacin en las Funciones ProgramacinenC
variables locales. Por ejemplo, considerar estos dos funciones:
func1()
{
int x ;
x = 10 ;
}
func2()
{
int x ;
x = -199 ;
}
Aqu la variable entera x se declara dos veces, una vez en func1() y
otra en func2(). La x en func1() no tiene conexin, o relacin, con la x de
func2() ya que cada x se conoce slo por el cdigo que est en el mismo bloque
que la declaracin de la variable.
Como se ha visto, el lenguaje C contiene la palabra clave auto, que se
puede usar para declarar variables locales. Sin embargo, como C por defecto
asume que todas las variables son NO globales, auto prcticamente no se usa.
Es prctica comn declarar todas las variables que se necesitan en una
funcin al comienzo del bloque de cdigo de la funcin (facilita la lectura
del cdigo al saber qu variables son usadas en la funcin). Sin embargo, no
es necesario hacerlo porque se pueden declarar variables locales en cualquier
bloque del cdigo.
Considerar esta funcin:
f()
{
char ch ;
printf("continuar (S/N) ?: ") ;
ch = getche() ;
if(ch == s) /* si contesta s se entra en el bloque */
{
char s[80] ; /* esto se "ve" dentro del if */
printf("introducir nombre: ") ;
gets(s) ;
proceso_1(s) ;
}
}
Aqu f() crea la variable local s desde la entrada en el bloque de
cdigo if y la destruye en la salida. Adems, s se conoce slo en el bloque
de if y no puede referenciarse en ninguna parte - incluso en otras partes de
la funcin que la contiene -.
La principal ventaja de declarar una variable local en un bloque
condicional es que la computadora asignar memoria para la variable slo si
se necesita. La razn para esto es que las variables locales no existen hasta
que el ordenador entra en el bloque en que se declaran.
Como el ordenador crea y destruye las variables locales con cada entrada
y salida del bloque en el que se declaran, sus contenidos se pierden
1
una vez
que la computadora deja el bloque.
1
Excepto aquellas variables que se declaren con el modificador static.
Cap.6-3
Captulo 6: Profundizacin en las Funciones ProgramacinenC
PARMETROS FORMALES
En el captulo 1 se estudi el hecho de que si una funcin va a utilizar
argumentos, deben declararse variables que acepten los valores de estos
argumentos. Aparte de recibir parmetros de entrada de la funcin, se
comportan como otras variables locales dentro de la funcin. Estas variables
se llaman parmetros formales de la funcin.
Recordar asegurarse que los parmetros formales que se declaran son del
mismo tipo que los argumentos que se usarn en la llamada de la funcin.
Adems, incluso aunque estas variables realicen la tarea especial de recibir
el valor de los argumentos que se pasan a la funcin, se pueden usar como
cualquier otra variable local.
VARIABLES GLOBALES
Al contrario de las variables locales, las globales se conocen a travs
del programa entero y se pueden usar en cualquier trozo de cdigo. En esencia,
su mbito es global al programa. Las variables globales tambin guardarn sus
valores durante la ejecucin entera del programa. Se pueden crear variables
globales declarndolas fuera de cualquier funcin. Cualquier funcin puede
acceder a ellas sin tener en cuenta en qu funcin est dicha expresin.
El almacenamiento para variables globales es una regin fija de memoria
que el compilador de C establece para este propsito. Las variables globales
son utilsimas cuando se usa el mismo dato en muchas funciones del programa.
Sin embargo, se debera evitar el uso innecesario de las variables globales,
por tres razones:
- Ocupan memoria durante toda la ejecucin del programa y no slo cuando
se necesita.
- El uso de una variable global en lugar de una local restringir la
generalidad de una funcin ya que depende de una variable que debe
definirse fuera de s misma.
- Usando demasiadas variables globales en un programa de grandes
dimensiones, puede producirse el cambio accidental del valor de una
variable debido al uso en cualquier parte del programa.
Como se puede ver, el siguiente fragmento de cdigo declara la variable
cont fuera de todas las funciones. Su declaracin se produce antes de la
funcin main(). Sin embargo, se podra poner en cualquier sitio, con tal de
que no est antes de la primera funcin usada. La prctica comn indica que
es mejor declarar todas las variables globales al principio del programa.
Cap.6-4
Captulo 6: Profundizacin en las Funciones ProgramacinenC
#include <stdio.h>
int cont; /* cont es global */
main()
{
cont = 100 ;
func1() ;
}
func1()
{
func2() ;
printf("cont es %d", cont) ; /* imprimir 100 */
}
func2()
{
int cont ; /* cont es local */
for(cont = 1 ; cont < 10 ; cont++)
printf(".") ;
}
Si se estudia este fragmento de programa cuidadosamente, se aclarara
que, aunque ni main() ni func1() tienen declarada la variable cont, ambas la
usan. Sin embargo, la funcin func2() ha declarado una variable local llamada
cont. Cuando se referencia cont, func2() trabajar slo a su variable local,
no a la global. Recordar que, si una variable global y una local tienen el
mismo nombre, todas las referencias a ese nombre de variable dentro de la
funcin en la que se declara como local se referir a la local y no afectar
a la variable global. Este hecho es un beneficio. Sin embargo, el olvido de
esto puede provocar que el programa acte extraamente -incluso parezca
correcta-.
Una de las caractersticas principales de un lenguaje estructurado es
la compartamentacin (compartimentos independientes) del cdigo y los datos.
En C se construye la compartamentacin usando variables locales y funciones.
Por ejemplo, he aqu dos formas de escribir mul(), que es una funcin sencilla
que calcula el producto de dos enteros:
GENERAL ESPECFICA
int x, y ;
mul(int x, int y) mul()
{ {
return(x * y) ; return(x * y) ;
} }
Ambas funciones devuelven el producto de las variables x e y. Sin
embargo, se puede usar la versin generalizada, o parametrizada, para devolver
el producto de cualesquiera dos nmeros, mientras que se puede usar la versin
especfica para encontrar slo el producto de las variables globales x e y.
A continuacin se presenta las diversas perspectivas en el siguiente
programa.
El cdigo que est en un mbito ms interno tiene conocimiento de los
mbitos externos. Sin embargo, el cdigo en mbitos ms externos no tiene
efecto o conocimiento del de mbito ms interno.
Se debera entender los diversos mbitos en este ejemplo si se estudio
concienzudamente.
Cap.6-5
Captulo 6: Profundizacin en las Funciones ProgramacinenC
#include <stdio.h> /* MBITO: un programa con varios mbitos */
#include <string.h>
int cont ; /* global a todo el programa */
main()
{
char str[80] ; /* local a main() */
printf("introduzca una cadena: ") ;
gets(str) ;
}
play(char *p) /* p es local a play */
{
if(!strcmp(p, "suma"))
{
int a, b ; /* local al bloque if */
printf("introduzca dos enteros: ") ;
scanf("%d%d", &a, &b) ;
printf("%d\n"), a + b) ;
}
else if(strcmp(p, "beep")) /* a y b no se conocen aqu */
printf("%c", 7) ;
}
6.3 ARGUMENTOS Y PARMETROS DE FUNCIONES
Como se ha visto anteriormente, se debe asegurar que los parmetros
formales de funcin son del mismo tipo que los argumentos que se usan en la
llamada a la funcin. Si hay un error de tipo, el compilador no dar un
mensaje de error pero se producirn resultados inesperados.
Al contrario que en otros lenguajes, C es muy robusto y generalmente
har algo -incluso si no se quiere- . Por ejemplo, si una funcin espera un
argumento entero pero se llama con un float, entonces el compilador usar
el primero de los dos bytes del float como el valor entero !. Como programador
se tiene la responsabilidad de asegurar que no se producen tales errores.
LLAMADA POR VALOR Y POR REFERENCIA (USO DE LOS PUNTEROS)
Dentro del funcionamiento de las funciones, hasta el momento sus
argumentos se han pasado por valor, es decir, se toma una copia y se utiliza
en una rutina determinada, pero el valor original no se alteraba. No es
suficiente escribir, por ejemplo:
void intercambio(int a, int b) ; /* indicar a y b es opcional */
Donde la funcin intercambio est definida como:
void intercambio(int x, int y) /* incorrecto */
{
int temporal ;
temporal = x ;
x = y ;
y = temporal ;
}
Cap.6-6
Captulo 6: Profundizacin en las Funciones ProgramacinenC
Para obtener los valores deseados, esto es, la modificacin de las
variables que intervienen en la funcin que llama a la rutina que las utiliza,
el procedimiento consiste en que los argumentos sean referenciados por la
direccin que ocupa, dicho de otra forma, utilizando punteros a los valores
que se modifican:
intercambio(&a, &b) ;
Como & seala la posicin que ocupa la variable a dentro del sistema en
ningn momento se desplaza o se copia su contenido. Dentro de la funcin
receptora los parmetros se declaran como punteros que reciben la direccin
indicada a la llamada. Con lo cual se tiene acceso directo a los contenidos
originales de las variables implicadas en ambas funciones:
void intercambio(int *dir_x, int *dir_y)
{
int temporal ;
temporal = *dir_x ;
*dir_x = *dir_y ;
*dir_y = temporal ;
}
Grficamente se representara como:
Puntero genrico (tipo void) como argumento
En C puede aparecer el siguiente fragmento de cdigo:
int valor( void *valor1, void *valor2) ;
Que representa un prototipo de funcin donde de se declara una funcin
que devuelve un valor entero y que posee dos argumentos de tipo puntero a
void.
Un puntero void es un puntero que puede sealar a cualquier tipo de dato
sin utilizar un molde de tipo. A esto se le conoce normalmente como puntero
genrico.
Los puntero void se utilizan para dos propsitos principales:
Cap.6-7
Captulo 6: Profundizacin en las Funciones ProgramacinenC
A) Son un modo de que una funcin reciba un puntero a cualquier tipo de
dato sin que se produzca un error de discordancia de tipo (motivo por
el que se cre el puntero void).
B) Permitir que una funcin devuelva un puntero genrico.
Cualquier puntero se puede convertir a tipo void * sin prdida de
informacin. Si el resultado se regresa al tipo de puntero original, ste es
recuperado.
Los punteros pueden ser asignados hacia y desde punteros de tipo
void * y puede ser comparados con ellos.
Paso de una tabla (con puntero) a una funcin
Cuando una tabla se lleva a una funcin, lo que se pasa es la posicin
del primer elemento. Para la funcin llamada, este argumento es una variable
local, es decir, un parmetro formado por el nombre de una tabla (que
representa un PUNTERO o variable que contiene la direccin de su primer
elemento). Se podra utilizar este hecho para escribir otra versin de la
funcin strlen() (devuelve la longitud de la cadena indicada):
/* las llamadas vlidas pueden ser:
strlen("esto es la cadena") ; -> constante cadena
strlen(tabla) ; -> char tabla[100]
strlen(puntero) ; -> char *puntero */
/* retorna con la longitud de la cadena */
int strlen(char *s)
{
int n ;
for (n = 0 ; *s != NULL ; s++)
n++ ;
return n ;
}
Puesto que es un puntero, es legal incrementarlo; s++ no tiene efecto
sobre la cadena de caracteres de la funcin que llam a strlen().
Ya que los parmetros formales en una definicin de funcin:
char s[] ;
char *s ;
Son equivalente, se prefiere el ltimo porque indica ms explcitamente
que el parmetro es un puntero.
Es posible trasladar PARTE de una tabla a una funcin, pasando el
puntero al inicio de la porcin de tabla. Por ejemplo, si a es una tabla:
/* formas de llamar a la funcin */
f(&a[2])
f(a + 2)
Pasan a la funcin f() la direccin una parte de la tabla (subarreglo
o subtabla) que inicia en [2].
Cap.6-8
Captulo 6: Profundizacin en las Funciones ProgramacinenC
Dentro de f(), la declaracin de parmetros puede ser:
f(int aa[]) {...} o
f(int *aa) {...} ;
por tanto la funcin f() slo "ve" la tabla a partir de la tercera
celdilla:
Si se est seguro de que los elementos existen, tambin es posible
indexar hacia atrs una tabla: aa[-1], aa[-2], etc.; son legtimas desde el
punto de vista sintctico y se refieren a elementos que preceden
inmediatamente a aa[0].
Para la funcin f()
Por supuesto es ilegal hacer referencia a celdillas que no estn dentro
de los lmites de la tabla.
Paso de cadena de caracteres a funciones
Para ilustrar el tratamiento de cadenas de caracteres dentro de
funciones que han recibido los argumentos con punteros, se estudiarn dos
funciones de la biblioteca estndar.
La primera funcin es strcpy(destino, origen), que copia la cadena
origen a la cadena destino. Sera agradable decir simplemente destino =
origen, pero esto copia la direccin contenido en el puntero, no el contenido
de la direccin sealada.
Para copiar los caracteres de las cadenas se requiere un bucle. Primero
se presenta la versin con tablas:
/* strcpy() versin 1, de ndices */
void strcpy(char *destino, char *origen)
{
int i ;
i = 0 ;
while((destino[i] = origen[i]) != \0) /*\0 equivale a NULL*/
i++ ;
}
En contraste, aqu est una versin de strcpy() con punteros:
Cap.6-9
Captulo 6: Profundizacin en las Funciones ProgramacinenC
/* strcpy() versin 2, de punteros */
void strcpy(char *destino, *origen)
{
while((*destino = *origen) != \0)
{
destino ++ ;
origen ++ ;
}
}
Aqu hay punteros convenientemente inicializados, que se desplazan a lo
largo de la tabla un carcter cada vez, hasta que el carcter nulo que
contiene origen se ha copiado en destino.
En la prctica no se escribira como se mostr anteriormente. Los
programadores expertos en C preferiran:
void strcpy(char *destino, char *origen)
{
while((*destino++ = *origen++) != \0) ;
}
Esto traslada los incrementos de destino y origen hacia dentro del
elemento de control del bucle. El valor *origen++ es el carcter al que seala
origen antes de incrementarse; el ++ postfijo no modifica destino sino hasta
despus de que se ha tomado un carcter. En la misma forma, el carcter se
almacena en la posicin anterior de destino ANTES de que destino se
incremente. Tambin este carcter es el valor de control el cul se compara
con \0 para controlar al bucle. El efecto real es que los caracteres se
copian de origen a destino, hasta el \0 final incluyndolo.
Obsrvese que una comparacin contra \0 es redundante, puesto que la
pregunta es simplemente si la expresin es cero. As la funcin podra
escribirse correctamente como:
void strcpy(char *destino, char *origen)
{
while(*destino++ = *origen++) ;
}
La conveniencia de esta notacin es considerable, y debe dominarse el
estilo, puesto que se encontrar frecuentemente en programas escritos en C.
La segunda rutina que se examinar es strcmp(destino, origen) que
compara las cadenas de caracteres destino y origen, y regresa un valor
negativo, cero o positivo si destino es lexicogrficamente menor que, igual
a o mayor que origen.
El valor se obtiene al restar los caracteres de la primera posicin en
que destino y origen no coinciden.
int strcmp(char *destino, char *origen)
{
int i ;
for(i = 0 ; destino[i] == origen[i] ; i++)
if(destino[i] == \0)
return 0 ;
return destino[i] - origen[i] ;
}
Cap.6-10
Captulo 6: Profundizacin en las Funciones ProgramacinenC
La versin con punteros de strcmp() es:
int strcmp(char *destino, *origen)
{
for(;*destino == *origen ; destino++, origen ++)
if(*destino == \0)
return 0 ;
return *destino - *origen ;
}
6.4 PASO DE ARGUMENTOS DESDE EL -DOS- A MAIN() (argc, argv y env)
A veces es til pasar informacin a un programa cuando se ejecuta.
Generalmente se pasa informacin a main() usando los argumentos de la lnea
de rdenes. Un argumento de lnea de rdenes es la informacin que sigue al
nombre del programa en el indicativo del sistema. Por ejemplo, se puede copiar
un fichero a otro desde la lnea de rdenes tecleando:
c:\>copy origen.txt destino.bkp
Donde copy es el programa que se desea ejecutar y se pasan origen.txt
y destino.bkp como los argumentos (elementos sobre los cuales acta copy).
Para main() se usan dos argumentos especiales predefinidos:
A) argc y argv ("argument count" y "argument vector" respectivamente)
. Encargados de recibir los argumentos de la lnea de rdenes.
B) env
2
("environment")
. Se utiliza para acceder a las variables de entorno del DOS
activas al mismo tiempo que el programa comienza la ejecucin.
Estos son argumentos que puede tener main() se suelen declara como:
main( int argc, char *argv[], char *env[] )
{......}
El parmetro argc mantiene el nmero de argumentos de la lnea de
rdenes y es un entero. Siempre es AL MENOS 1 ya que el nombre del programa
se erige en el primer argumento. El parmetro argv es un puntero a una tabla
de punteros de caracteres. Es decir, argv, es el puntero de una tabla de
cadenas. Cada elemento en esta tabla apunta a un argumento del prompt.
Los corchetes indican que argv es una tabla de una longitud
indeterminada. Se puede acceder ahora a los argumentos individuales indexando
argv[]. En versiones del DOS anteriores a 3.0 argv[0] estaba vaco.
Todos los argumentos de la lnea de rdenes son cadenas (un programa
debe convertir cualquier nmero al formato interno adecuado).
En el ejemplo anterior, argc es 3, y argv[0], argv[1] y argv[2] son
"copy", "origen.txt" y "destino.bkp", respectivamente. El primer argumento
optativo (el obligatorio es el propio nombre del programa) es argv[1] y el
2
El C estndar ANSI solamente define los parmetros argc y argv. Sin embargo puede que algunos
compiladores permitan parmetros adicionales a main(). Por ejemplo, la mayora de compiladores basados en
DOS (como Turbo C) ofrecen el acceso a informacin del entorno DOS.
Cap.6-11
Captulo 6: Profundizacin en las Funciones ProgramacinenC
ltimo es argv[argc - 1]; adems, el estndar requiere argv[argc] sea un
puntero nulo.
El siguiente programa breve ilustra el uso de los argumentos de la lnea
de rdenes e imprimir en pantalla Hola seguido por el nombre si se teclea el
nombre directamente despus del nombre del programa.
#include <stdio.h>
#include <process.h>
main(int argc, char *argv[]) /* nombre del programa */
{
if (argc != 2)
{
printf("falta especificar el nombre\n") ;
exit(1) ;
}
printf("Hola %s", argv[1]) ;
}
Si se llama este programa nombre y el nombre indicado es usuario_1
entonces debera escribir en el prompt "nombre usuario_1". La salida desde el
programa debera ser hola usuario_1. Por ejemplo, si se est en la unidad C:
y se ejecuta MS-DOS, se ver:
c:\>NOMBRE usuario_1
hola usuario_1
c:\>
Otro ejemplo sencillo es el programa echo, que despliega sus argumentos
de la lnea de rdenes en una lnea, separado por blancos. Esta es la orden:
c:\> echo primero, segundo
Imprime por pantalla:
primero, segundo
Para implementar este programa se presentan dos versiones:
A) trata argv[] como una tabla de punteros a carcter:
Cap.6-12
Captulo 6: Profundizacin en las Funciones ProgramacinenC
#include <stdio.h>
main(int argc, char *argv[]) /* eco de los argumentos en prompt */
{
int i ;
for(i = 1; i < argc ; i++)
printf("%s%s", argv[i], (i < argc - 1) ? " ":"") ;
printf("\n") ;
return 0 ;
}
B) Como argv es un puntero a una tabla de punteros, se puede manipular
el puntero en vez de indexar la tabla.
#include <stdio.h>
main(int argc, char *argv[]) /* Idem anterior, 2 versin */
{
while(--argc > 0)
printf("%s%s", *++argv, (argc > 1) ? " ":"") ;
printf("\n") ;
return 0 ;
}
Puesto que argv es un puntero al principio de la tabla de cadenas de
argumentos, incrementarlo en 1 (++argv) lo hace apuntar hacia argv[1] en lugar
de sealar argv[0]. Cada incremento sucesivo lo mueve al siguiente argumento;
entonces *argv es el puntero a ese argumento. Al mismo tiempo, argc disminuye;
cuando llega a cero, no quedan argumentos por imprimir.
En forma alternativa, se puede escribir la proposicin printf como:
printf((argc > 1) ? "%s ":"%s", *++argv) ;
Esto demuestra que el argumento de formato de printf() tambin puede ser
una expresin.
Se puede separar cada argumento de la lnea de rdenes por un espacio
o un tabulador. El lenguaje C no considera como separadores las comas ",", los
puntos y comas ";" y similares. Por ejemplo:
uno,dos,tres
Se trata de una cadena porque las comas no son separadores autorizados.
Si se necesita pasar un argumento de lnea de orden que contenga
espacios, debe colocarse entre comillas. Se tratar a ste como un argumento
de lnea de orden:
"Este es un argumento"
Un programa interesante y til en utilizar argumentos de lnea de
rdenes. Utiliza la funcin system() de Turbo C para ejecutar una serie de
rdenes de MS-DOS que fueron introducidas en la lnea de rdenes:
Cap.6-13
Captulo 6: Profundizacin en las Funciones ProgramacinenC
/* COMLINEA: programa que ejecuta los comandos de MS-DOS
especificados en la lnea de comandos */
#include <stdio.h>
#include <stdlib.h>
main(int argc, char *argv[])
{
int i ;
for(i = 1 ; i < argc ; i ++)
system(argv[i]) ;
}
Suponiendo que llamamos a este programa comlinea, la siguiente lnea de
rdenes hace que en ese secuencia se ejecuten las de MS-DOS: ver, dir *.c y
tree.
c:\>comlinea ver "dir *.c" tree
Puede sorprender la cantidad de utilidades que tiene este programa.
Para acceder a un carcter individual en una de las cadenas de rdenes,
debe Aadirse un segundo ndice a argv[]. Por ejemplo, el siguiente programa
visualiza en la pantalla un carcter a la vez que todos los argumentos con los
que fue llamado.
/* Imprime todos los comandos especificados
en la lnea de argumentos */
#include <stdio.h>
main(int argc, char *argv[])
{
int t, i ;
for(t = 0 ; t < argc ; ++t)
{
i = 0 ;
while(argv[t][i])
{
printf("%c", argv[t][i]) ;
++i ;
}
printf(" ") ;
}
}
Recordar que el primer ndice accede a la cadena y el segundo al
carcter de la misma.
Otra forma de acceder a cada carcter sera:
*++argv[t]
Que incrementa el puntero argv[t].
Sin embargo si se hubiera utilizado la siguiente sentencia:
Cap.6-14
Captulo 6: Profundizacin en las Funciones ProgramacinenC
(*++argv)[0] /* equivale a indicar **++argv */
Considerando que *++argv (visto en ejemplos anteriores) es un puntero
a un argumento de tipo cadena, se puede decir que (*++argv)[0] es su primer
carcter. Esto es debido a que los parntesis tiene ms prioridad que "*" y
"++".
En C se pueden utilizar tantos argumentos en la lnea de rdenes como
permita el sistema operativo. DOS limita a una lnea de 128 caracteres.
Normalmente se usan estos argumentos para indicar un nombre de archivo o una
opcin. Usando los argumentos de la lnea de rdenes se dar al programa una
apariencia profesional y facilitar el uso del programa en archivo por lotes.
Recordar que el parmetro env se declara del mismo modo que argv. Es un
puntero a una tabla de cadenas que contiene especificaciones de entornos. La
ltima cadena en la tabla es nula y es la que marca el final. Este programa
imprime todas las cadenas de entornos actuales:
#include <stdio.h>
main(int argc, char *argv[], char *env[])
{
int i ;
for(i = 0 ; env[i] ; i++)
printf("%s\n", env[i]) ;
}
Observar que deben declararse tanto los parmetros de argc como de argv
aunque no se utilicen, porque las declaraciones de parmetros dependen de la
posicin. Sin embargo, es perfectamente vlido dejar la declaracin env
aparte, si no se utiliza.
6.5 RECURSIVIDAD
La recursividad es una tcnica potente de programacin que puede
utilizarse en lugar de la iteracin (bucles) para resolver determinado tipo
de problemas. Consiste en permitir que una funcin se llame a s misma para
resolver una versin reducida del problema original. Esta herramienta slo
est disponible en algunos lenguajes de programacin, como el Pascal y el C.
Frente a una determinada gama de problemas se puede optar por una
solucin iterativa o una recursiva.
Se dice que una rutina es recursiva si entre sus instrucciones tiene una
llamada a ella misma.
El uso de esta tcnica es apropiado especialmente cuando el problema a
resolver o la estructura de datos a procesar tiene una clara definicin
recursiva.
Por ejemplo, si se desea calcular el factorial de un nmero n, entero
positivo, se har a partir de su definicin:
0! = 1
n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1, si n > 0
Esta definicin dara lugar a una solucin iterativa, que se puede
representar mediante la siguiente rutina:
Cap.6-15
Captulo 6: Profundizacin en las Funciones ProgramacinenC
int factorial(int dato) /* valor mayor o igual a 0 */
{
int f ;
f = 1 ;
while(dato > 0 )
{
f = f * dato ;
dato -- ;
}
return f ; /* devuelve el valor del factorial */
}
Pero existe esta otra definicin "recursiva" de la funcin factorial:
0! = 1
n! = n * (n - 1)!, si n > 0
Esta segunda definicin, para el clculo del factorial de un nmero
mayor que cero, hace referencia propia a la funcin, lo cual da lugar a una
solucin recursiva, que se representa en la siguiente funcin:
#include <stdio.h>
#include <conio.h>
int factorial(int dato) ;
main()
{
int valor ;
clrscr() ;
printf("Introduzca dato para calcular de su factorial: ") ;
scanf("%d", &valor) ;
valor = factorial(valor) ;
printf("\n\nSu factorial es: %d", valor) ;
getche() ;
clrscr() ;
}
int factorial(int dato)
{
int f ;
if(dato == 1) return(1) ;
f = factorial(dato - 1) * dato ; /* no vale n-- ni --n */
return (f) ;
}
Si se llama a factorial() con un argumento de 1, la funcin devuelve 1,
si se hace la llamada con cualquier otro argumento, devuelve el producto de
factorial(dato - 1) * dato. Para evaluar esta expresin, se llama a
factorial() con dato - 1 recursivamente. Este proceso contina hasta que n es
igual a 1. En ese instante comienza el retorno de las sucesivas llamadas.
Cuando se calcula el factorial de 2, la primera llamada a factorial()
provocar una segunda llamada con argumento de 1. Esta segunda llamada
Cap.6-16
Captulo 6: Profundizacin en las Funciones ProgramacinenC
devolver 1, que se multiplica entonces por 2 (valor original de dato). La
respuesta es entonces 2. Se podra encontrar interesante insertar sentencias
printf() en factorial(), que mostrarn a qu nivel est cada llamada y cul
es el valor de las respuestas intermedias.
Cuando una funcin se llama a s misma, el ordenador asigna sitio en la
pila para variables y parmetros nuevos y ejecuta el cdigo de la funcin con
estas nuevas variables desde el comienzo. Una llamada recursiva no hace una
nueva copia de la funcin. Slo son nuevos los argumentos. Como cada llamada
recursiva vuelve, el ordenador devuelve las variables locales y parmetros
viejos desde la pila y reanuda la ejecucin en el momento de la llamada de
funcin en la funcin.
La mayora de las rutinas recursivas no ahorran significativamente
tamao de cdigo o almacenamiento de variables. Tambin las versiones
recursivas de la mayora de las rutinas pueden funcionar ligeramente ms
lentamente que sus equivalente iterativos debido a las llamadas de funcin
aadidas. Aunque, al contrario, muchas llamadas recursivas a una funcin
podran causar el desbordamiento de la pila. Como el almacenamiento de los
parmetros y las variables locales de la funcin estn en la pila, y cada
nueva llamada crea nueva copia de estas variables, es posible que la pila
solape otra porcin de la memoria por ejemplo la reservada a datos o al cdigo
del programa. Sin embargo, probablemente no hay que preocuparse por este
problema a menos que se desboque una funcin recursiva.
La ventaja principal de las funciones recursivas es que se pueden usar
para crear versiones ms claras y sencillas de los algoritmos que sus
correspondientes iterativas. Por ejemplo, el algoritmos de ordenacin:
QuickSort (ordenacin rpida) muy difcil e implementar de forma iterativa.
Una posible versin (no la ms rpida pero s una de las ms simples)
de este mtodo de ordenacin. Se emplea el elemento intermedio de cada
subcadena para "dividir" el camino de bsqueda:
/* ordena_rap(): ordena valor[izquierdo]....valor[derecho]
en orden ascendente. */
void ordena_rap(int v[], int izq, int dcha)
{
int i, ulti ;
void cambio(int v[], int i , int j) ; /* prototipo de f. */
if(izq >= dcha) /* si la tabla tiene > de 2 datos */
return;
cambio(v, izq, (izq + dcha)/2) ;
ulti = izp ;
for(i = izq + 1; 1 <= derecha ; i++)
if(v[i] < v[izq])
cambio(v, ++ulti, i) ;
cambio(v, izq, ulti) ;
ordena_rap(v, izq, ulti - 1) ;
ordena_rap(v, ulti + 1, dcha) ;
}
Se pasa la operacin de intercambio a una funcin separada cambio(),
puesto que ocurre tres veces en ordena_rap.
Cap.6-17
Captulo 6: Profundizacin en las Funciones ProgramacinenC
/* cambio(): intercambia v[i] y v[j] */
void cambio(int v[], int i, int j)
{
int temporal ;
temporal = v[i] ;
v[i] = v[j] ;
v[j] = temporal ;
}
La biblioteca estndar incluye una versin de ordena_rap() que puede
ordenar objetos de cualquier tipo.
Cuando se escribe una funcin recursiva, se debe tener en algn lugar
una sentencia if para forzar a la funcin a volver si se ejecuta la llamada
recursiva. Si no se hace as, despus de llamar a la funcin nunca se
volvera.
Observar otro ejemplo de funcin recursiva. Se denomina sirena y para
crear un sonido de sirena ascendente utiliza las funciones sound() y
nosound(). A la funcin sound() se le llama con un argumento de enteros que
se convertir en la frecuencia del sonido producido por el altavoz del
ordenador. El sonido contina hasta que el programa llama a nosound(), que no
toma argumentos. La funcin sirena se llama as misma hasta que la frecuencia
es menor de 100. En este momento las llamadas recursivas comienzan a
desenmaraarse y se produce el ruido de sirena.
/* programa que emite un sonido de sirena */
#include <stdio.h>
#include <dos.h>
int sirena(int freq) ;
main()
{
sirena(10000) ;
}
sirena(int freq)
{
int i ;
if(freq > 100)
sirena(freq - 100 ) ;
sound(freq) ; /* activa el sonido */
for(i = 0 ; i < 10000 ; i ++) ; /* retardo */
nosound() ; /* desactiva el sonido */
}
6.5 PUNTEROS A FUNCIONES
Una caracterstica poderosa particularmente confusa es el puntero de una
funcin. En este sentido, un puntero de funcin es un nuevo tipo de datos.
Incluso aunque una funcin no es una variable, tiene una posicin fsica en
memoria que se puede asignar a un puntero. La direccin asignada al puntero
es el punto de entrada a la funcin. Se puede entonces usar este puntero en
lugar del nombre de la funcin. El puntero tambin permite a las funciones
pasarse como argumento a otras funciones.
Cap.6-18
Captulo 6: Profundizacin en las Funciones ProgramacinenC
Como probablemente ya se sabe, cuando el compilador compila un programa
crea un punto de entrada para cada funcin del programa. Despus de enlazar
("linkar") el programa, el punto de entrada tiene una direccin fsica, a la
que se llama cada vez que se referencia una funcin. Dado que esto es una
direccin, es posible hacer que una variable puntero seale a ella. Una vez
que se tiene un puntero a una funcin, es posible llamar a esa funcin
utilizando el puntero.
Para crear una variable que pueda apuntar a una funcin, se declara el
puntero del mismo tipo que el tipo que devuelve la funcin, seguido de los
parmetros (estos son opciones, pensar que el puntero a funcin puede sealar
en momentos distintos a diferentes funciones).
Un puntero se declara como:
tipo (*nom_pun)() ; /* entre los parntesis pueden ir argumentos */
Los parntesis que rodean a *nom_pun son necesarios debido a las reglas
de prioridad de C.
Para asignar la direccin de una funcin a un puntero a funcin,
simplemente se utiliza su nombre sin ningn parntesis. Por ejemplo suponiendo
que sum() tiene el prototipo:
int suma(int a, int b) ;
La sentencia de asignacin:
nom_pun = suma ;
Es correcta. Una vez que se ha hecho esto, se puede llamar
indirectamente a sum() a travs de nom_pun utilizando una sentencia como:
resultado = (*nom_pun) (10, 20) ;
De nuevo, debido a las reglas de prioridad de C, son necesarios los
parntesis alrededor de *nom_pun.
A continuacin se muestran dos ejemplos que muestran el uso de punteros
a funciones:
#include <stdio.h>
#include <string.h>
void check(char *a, char *b, int (*cmp)()) ;
main()
{
char s1[80], s2[80] ;
int (*p)() ; /* declara un puntero a funcin */
p = strcmp ; /* asigna el lugar de la funcin al puntero */
printf("Introduzca 1 cadena: ") ;
gets(s1) ;
printf("Introduzca 2 cadena: ") ;
gets(s2) ;
check(s1, s2, p) ; /* p pasa la direccin de la funcin */
}
void check(char *a, char *b, int (*cmp)())
{
printf("verificando su igualdad\n") ;
Cap.6-19
Captulo 6: Profundizacin en las Funciones ProgramacinenC
if(! (*cmp)(a, b))
printf("igual") ;
else
printf("no igual") ;
}
Cuando el ordenador llama a la funcin check(), se pasan dos punteros
de carcter y uno de funcin como parmetros. La funcin check() declara los
argumentos como punteros de carcter y un puntero de funcin. Obsrvese cmo
se declara el puntero de funcin.
Una vez dentro de check(), se puede ver cmo la funcin strcmp() se
llama. La sentencia:
(*cmp) (a, b)
Realiza la llamada a la funcin - en este caso, strcmp() - a la que
apunta *cmp. Esta sentencia tambin representa el formato general de usar un
puntero de funcin para llamar la funcin a la que apunta.
#include <stdio.h>
int suma(int a, int b) ;
main()
{
int (*p) (int a, int b) ;
int resultado ;
p = suma ; /* obtiene la direccin de suma() */
resultado = (*p) (10, 20) ;
printf("%d", resultado) ;
}
suma(int a, int b)
{
return a + b ;
}
El programa pide dos nmeros al usuario, llama a suma() indirectamente
utilizando p, y muestra el resultado.
Uno de los usos ms importantes de los punteros a funciones es cuando
se crea una tabla de punteros a funcin. Cada elemento de la tabla puede
apuntar a una funcin diferente. Para llamar a una funcin especfica
simplemente se indexa la tabla. Una tabla de punteros a funcin permite
escribir un cdigo muy eficiente cuando se tiene que llamar a un conjunto de
funciones distintas bajo diferentes circunstancias. Las tablas de puntero a
funcin se utilizan normalmente cuando se escribe software de sistemas, como
compiladores, ensambladores e intrpretes. Sin embargo, no se limitan a estas
aplicaciones.
Aunque es difcil encontrar ejemplos de tablas de punteros a funcin que
sean breves y a vez significativos, el programa que se muestra a continuacin
insina su potencia. Igual que el ejemplo anterior, ste pide al usuario que
introduzca el nmero de la operacin que quiere llevar a acabo. Este nmero
despus se utiliza directamente para indexar la tabla de punteros a funcin
para ejecutar la funcin apropiada. Por ltimo, se muestra el resultado.
Cap.6-20
Captulo 6: Profundizacin en las Funciones ProgramacinenC
#include <stdio.h>
int suma(int a, int b) ;
int rest(int a, int b) ;
int mult(int a, int b) ;
int divi(int a, int b) ;
int (*p[4]) (int a, int b) ;
main()
{
int resultado ;
int i, j, opc ;
p[0] = suma ; /* obtiene la direccin de suma() */
p[1] = rest ; /* obtiene la direccin de rest() */
p[2] = mult ; /* obtiene la direccin de mult() */
p[3] = divi ; /* obtiene la direccin de divi() */
printf("Introduzca dos nmeros: ") ;
scanf("%d%d", &i, &j) ;
printf("\n0: Sumar 1: Restar 2: Multiplicar 3: Dividir\n") ;
do
{
printf("Introduzca nmero de operacin (0 - 3): ") ;
scanf("%d", &opc) ;
}
while(opc < 0 || opc > 3) ;
resultado = (*p[opc]) (i, j) ;
printf("%d", resultado) ;
}
suma(int a, int b) { return a + b ; }
rest(int a, int b) { return a - b ; }
mult(int a, int b) { return a * b ; }
divi(int a, int b)
{
if(b) /* 0? */
return a / b ;
else
{
printf("no puede hacer la divisin: b = ") ;
return 0;
}
}
Cuando se estudie este cdigo, se entender que la utilizacin de una
tabla de punteros a funcin para llamar a la funcin apropiada es ms
eficiente que utilizar una sentencia switch.
Antes de abandonar este ejemplo, se puede utilizar para ilustrar un
punto ms: las tablas de punteros a funcin se pueden inicializar como
cualquier otra tabla. La siguiente versin ilustra esto:
Cap.6-21
Captulo 6: Profundizacin en las Funciones ProgramacinenC
#include <stdio.h>
int suma(int a, int b) ;
int rest(int a, int b) ;
int mult(int a, int b) ;
int divi(int a, int b) ;
/* inicializa la tabla de punteros */
int (*p[4]) (int a, int b) = { suma, rest, mult, divi } ;
main()
{
int resultado ;
int i, j, opc ;
printf("introduzca dos nmeros: ") ;
scanf("%d%d", &i, &j) ;
printf("0: Sumar 1: Restar 2: Multiplicar 3:Dividir\n") ;
do
{
printf("Introduzca nmero de operacin (0 - 3):") ;
scanf("%d", &opc) ;
}
while(opc < 0 || opc > 3) ;
resultado = (*p[opc]) (i, j) ;
printf("%d", resultado) ;
}
suma(int a, int b) { return a + b ; }
rest(int a, int b) { return a - b ; }
mult(int a, int b) { return a * b ; }
divi(int a, int b)
{
if(b) /* 0? */
return a / b ;
else
{
printf("no puede hacer la divisin b = ") ;
return 0 ;
}
}
6.7 FUNCIONES QUE DEVUELVEN PUNTEROS
Aunque se manejan funciones que devuelven punteros exactamente en la
misma forma que cualquier otro tipo de funcin, se necesita discutir algunos
conceptos importantes:
Los punteros a variables no son ni enteros ni enteros sin signo. Son
direcciones de memoria de ciertos tipos de datos. La razn para esta
distincin reside en el hecho de que, cuando el ordenador realiza la
aritmtica de punteros, es relativa al tipo base; es decir, si se incrementa
un puntero entero, contendr un valor dos veces mayor que su valor anterior.
Generalizando, cada vez que un puntero se incrementa apuntar al siguiente
dato de su tipo. Como cada tipo de dato puede ser de diferente longitud, el
compilador debe saber a qu tipo de dato est apuntando el puntero para
sealar exactamente al dato siguiente.
Por ejemplo, aqu est una funcin que devuelve un puntero a una cadena
colocado en el lugar donde el ordenador encuentra una coincidencia de
caracteres.
Cap.6-22
Captulo 6: Profundizacin en las Funciones ProgramacinenC
/* Retorna un puntero al primer carcter en s que es igual a c */
char *match(char c, char *s)
{
int cont = 0 ;
while(c != s[cont] && s[cont] != \0)
cont ++ ;
if(s[cont])
return(&s[cont]) ;
else
return (char *) \0 ;
}
La funcin match() trata de retornar con un puntero al lugar de la
cadena donde encuentra una coincidencia con c. Si no se encuentra la
coincidencia, la funcin devolver un puntero al terminador nulo. A
continuacin se muestra un programa que usa la funcin match():
#include <stdio.h>
#include <conio.h>
char *match(char c, char *s) ; /* prototipo de funcin */
main()
{
char s[80], *p, ch ;
printf("Introduzca una cadena y un carcter: ") ;
gets(s) ;
ch = getche() ;
p = match(ch, s) ;
if(p) /* aqu est la igualdad */
printf("%s", p) ;
else
printf("no se ha encontrado la ocurrencia") ;
}
Este programa lee primero una cadena y despus un carcter. Si el
carcter est en la cadena, entonces imprime la cadena desde la posicin del
carcter. De lo contrario, imprime "no ha encontrado la ocurrencia". Por
ejemplo, si se teclea Juan Madrid como la cadena y M como el carcter, el
programa responder con Madrid.
6.8 MODIFICADORES DEL TIPO DE FUNCIN (Turbo C)
El compilador de Turbo C define tres tipos de modificadores que se
pueden aplicar slo a funciones. Estos modificadores son:
- pascal
- cdecl
- interrupt
Cap.6-23
Captulo 6: Profundizacin en las Funciones ProgramacinenC
El estndar ANSI propuesto no define estos modificadores, pero Turbo C
los proporciona para tener mayor ventaja en el entorno de la programacin del
PC.
MODIFICADOR DE TIPO -pascal-
El modificador de tipo pascal le dice al compilador que use la
convencin de paso de parmetro del lenguaje Pascal para los argumentos de la
funcin, en vez del mtodo normal del lenguaje C. Este modificador permite dos
posibilidades:
1) Se pueden escribir la funciones en C que usarn otros compiladores.
2) Se pueden usar las rutinas de biblioteca de un compilador de Pascal
declarndolas al comienzo del programa C con el tipo pascal.
Por ejemplo, esta versin de la funcin int_pwr() puede llevarse a un
compilador de pascal:
pascal int_pwr(int m, register int e) /* para compilador de pascal */
{
register int temp ;
temp = 1 ;
for(; e; e--)
temp = temp * m ;
return temp ;
}
Para compilar todas las funciones en un archivo destinado a ser de tipo
pascal, podra hacerse sin utilizar pascal, usando la seleccin del men
principal <Options> -> <Compiler> -> <Code generation>. Se puede ahora
establecer la convencin de llamadas a Pascal. Aceptando esta opcin se
configura el compilador para que trate todas las funciones como si fuera a
usarlas un compilador de Pascal.
MODIFICADOR DE TIPO -cdecl-
La palabra reservada cdecl es el opuesto a pascal porque indica a Turbo
C que compile una funcin de manera que se pasen sus parmetros de forma
compatible con otras funciones C. Slo se usa cdecl cuando se configura el
compilador usando la convencin de llamadas de Pascal y se tienen unas pocas
funciones que se quieren compilar en el formato de pascal. Recordar que La
palabra reservada cdecl es especfica de Turbo C y no es transportable
generalmente.
MODIFICADOR DE TIPO -interrupt-
El modificador interrupt le dice al compilador que la funcin que
"modifica" se usar como un controlador de interrupciones. El modificador
provoca que Turbo C preserve todos los registros de la CPU cada vez que el
programa entre en la funcin y salga con una instruccin IRET (retorno de
interrupcin). Un ejemplo en la aplicacin de este modificador es en aquellas
funciones relacionadas con programas residentes (TSR Terminate and Stay -
Resident). El desarrollo y la instalacin de controladores de interrupciones
est fuera del alcance de este documento.
6.9 INSERCIN DE CDIGO EN LENGUAJE ENSAMBLADOR DENTRO DE UNA FUNCIN
En algunos casos, y especialmente en los procedimientos de interrupcin,
Cap.6-24
Captulo 6: Profundizacin en las Funciones ProgramacinenC
es necesario escribir las funciones en ensamblador.
Cuando se escribe un procedimiento en ensamblador, y en general en
cualquier otro lenguaje, aparecen siempre los siguientes problemas:
- La denominacin y las caractersticas de los segmentos de cdigo y
datos, para el caso en que el procedimiento se escriba en ensamblador.
- El intercambio de argumentos.
- La devolucin del valor que produce la funcin.
Por medio del compilador Turbo C el problema se simplifica bastante,
gracias a:
- El acceso directo de los registros del procesador.
- La posibilidad de incluir dentro de un programa en C instrucciones
escritas en ensamblador que tienen acceso a las variables del programa.
ACCESO A LOS REGISTROS
Un programa escrito en C puede acceder directamente en cualquier
momento, ya sea para la lectura o para la escritura, a los registros HARDWARE
del microprocesador gracias a la programacin en ensamblador, sobre todo para
la escritura. Basta con hacer referencia a las pseudovariables. Cuando Turbo
C las reconoce no les asigna memoria.
Las pseudovariables son:
_AX _AH -AL
_BX _BH _BL
_CX _CH _CL
_DX _DH _DL
_SP _BP
_DI _SI
_CS _DS _ES _SS
Es decir, los nombres de los registros precedidos de un carcter de
subrayado.
De esta manera la sentencia:
_AH = 4 ;
Coloca el valor 4 en el registro AH del microprocesador.
Es muy posible que la siguiente instruccin de C utilice el registro AH,
aunque el usuario no lo haya requerido. As pues, hay que ser prudente y, si
es preciso, comprobar el cdigo generado por el compilador de C; la opcin -S
del TCC.exe crea un archivo de extensin .asm que contiene todas las
instrucciones y declaraciones que corresponden al programa fuente.
INSTRUCCIONES EN ENSAMBLADOR
En un programa escrito en C puede incluirse una instruccin en
ensamblador con la condicin de que lleve el prefijo asm. El formato general
de esta declaracin es:
asm <cdigo_operacin> <operandos> ; /* o carcter de nueva lnea */
Adems, no slo es posible acceder a las variables del programa, sino
Cap.6-25
Captulo 6: Profundizacin en las Funciones ProgramacinenC
tambin efectuar bifurcaciones a las etiquetas
3
del programa C:
- jmp
- jz
- etc.
Recordar que una instruccin de este tipo termina con ";" o pulsando
<Enter>. Los comentarios se escriben como en C y no de acuerdo con las reglas
del programa ensamblador.
Estos programas deben compilarse desde el DOS con:
tcc -B nombre_fichero <
Si en el subdirectorio se encuentran los ficheros:
tasm.exe -> Turbo ensamblador
tlink.exe -> Turbo linkador
Cuando se produzca el compilado, si no existe ningn error de
compilacin, se realizarn automticamente las fases de:
1) Generacin del fichero en cdigo ensamblador.
2) Obtencin del mdulo objeto (nombre_fichero.obj) a partir del mdulo
fuente (nombre_fichero.asm).
3) Reubicado de los mdulos de librera y creacin del fichero
ejecutable (nombre_fichero.exe).
En caso de que no exista el fichero tasm.exe el procedimiento es el
siguiente:
- Utilizar un programa ensamblador del 80x86, por ejemplo el MASM, que
permita conseguir el fichero .obj a partir del programa fuente .asm.
- Ejecutar el linkador proporcionado por el programa. Para su uso deben
indicarse los siguiente parmetros:
tlink fich1.obj fich2.obj .. fichn.obj lib\c0x.obj, A, B, lib\cx.lib C
Donde:
fich1...fichn Son todos los ficheros objeto (.obj) que pretenden
agruparse en uno ejecutable.
lib\c0x.obj Indica el modelo de asignacin de memoria que se va
a reservar para el cdigo, variables globales, memoria
dinmica y pila al programa (para adaptarse a la
arquitectura de segmento-desplazamiento de la familia
80x86).
x puede ser:
t: tiny
s: small
m: medium
c: compact
l: large
h: huge
3
Recordar que en C una etiqueta referencia una posicin del programa y su representacin sera la
de un conjunto de caracteres terminados en dos puntos:
Cap.6-26
Captulo 6: Profundizacin en las Funciones ProgramacinenC
A Nombre del fichero ejecutable (.EXE). Si no se indica
ninguno, crea el fichero ejecutable con el nombre del
fichero fuente.
B Nombre del fichero que contiene un mapa de smbolos.
Es opcional.
lib\cx.lib Librera que contiene la mayora de las funciones de
propsito general incorporadas en el compilador.
x puede ser:
s, m, c, l o h
/* segn el modelo de memoria asignado */
C Puede acompaar una o varias libreras como por
ejemplo:
lib\graphics.lib -> funciones grficas.
lib\mathx (x segn modelo) -> funciones matemticas.
lib\fp87 -> funciones para el coprocesador matemtico.
lib\emu -> emulador del copro. matemtico (8087).
etc.
Un programa de ejemplo donde se insertan instrucciones en ensamblador
sera:
#include <stdio.h>
#include <conio.h>
main()
{
int i, k = 4 ;
clrscr() ;
printf("Este programa tiene sentencias en ensamblador\n") ;
asm mov CX, k ; /* pone el contenido de k en CX */
asm mov DX, CX ; /* Instruccin tpica: CX -> DX */
i = _DX ; /* Pseudovariable DX */
printf("i = %d \n", i) ;
}
Cap.6-27
Captulo 6: Profundizacin en las Funciones ProgramacinenC
EJERCICIOS PROPUESTOS, CAPTULO 6
1) Segn el mbito de las variables describe los siguientes tipos:
- Global - Local (a una funcin)
- Local (a un bloque) - Extern
- Esttica - Registro
Aydate de ejemplos en la explicacin.
2) Es correcto este programa?. Si no lo es, porqu?.
#include <stdio.h>
mifunc(int num, int min, int max) ;
main()
{
int i ;
printf("Introduzca un nmero entre 1 y 10: ") ;
mifunc(&i, 1, 10) ;
}
void mifunc(int num, int min, int max)
{
do
{
scanf("%d", num) ;
}
while(*num < min || * num > max) ;
}
3) Realizar un programa que solicite un dato numrico al usuario.
Recurrir a una funcin con llamada por referencia, a la que se debe llevar dos
argumentos:
A) el mensaje "Introduzca un nmero: " (utilizando punteros)
B) la direccin de la variable que contenga el n.
4) Repetir el programa anterior introduciendo una cadena de
caracteres en lugar de un entero.
5) Explicar el trabajo que realiza la siguiente funcin haciendo
especial hincapi en el parmetro formal void *v[].
void swap(void *tabla[], int x , int y)
{
void *temporal ;
temporal = v[x] ;
v[x] = v[y] ;
v[y] = temporal ;
}
Cap.6-28
Captulo 6: Profundizacin en las Funciones ProgramacinenC
6) Cules son los parmetros formales del siguiente prototipo de
funcin? :
ordenar(void *v[] ; int izq, int der, int(*comp)(void *, void *)) ;
7) Cmo se pasan los argumentos de una lnea de rdenes a un
programa en C ?.
8) Escribir un programa que contenga una funcin que tome como
argumento una cadena de caracteres y devuelva un puntero al primer dgito
(caracteres 0 al 9) que encuentre. Si la cadena no contiene ningn dgito
se devolver un NULL.
9) Estn declaradas correctamente los parmetros de las funciones
que van a recibir una tabla de enteros en el siguiente programa?. Razona tu
respuesta.
#include <stdio.h>
void f1(int num[5]), f2(int num[]), f3(int *num) ;
main()
{
int contador[5] = {1, 2, 3, 4, 5} ;
f1(contador) ;
f2(contador) ;
f3(contador) ;
}
void f1(int num[5])
{
int i ;
for(i = 0 ; i < 5 ; i++)
printf("%d", num[i]) ;
}
void f2(int num[])
{
int i ;
for(i = 0 ; i < 5 ; i++)
printf("%d", num[i]) ;
}
void f3(int *num)
{
int i ;
for(i = 0 ; i < 5 ; i++)
printf("%d", num[i]) ;
10) Realizar un programa que contenga una funcin recursiva para que
imprima los nmeros del 0 al 9 en la pantalla.
11) Desarrollar un programa que utilice la recursividad para copiar
una cadena en otra .
Cap.6-29
Captulo 6: Profundizacin en las Funciones ProgramacinenC
12) Qu est mal en esta funcin recursiva?. Razona tu respuesta.
void f()
{
int i ;
printf("en f() \n") ;
for(i = 0; i < 10 ; i++)
f() ;
}
13) Cuando se tienen que pasar datos numricos a un programa (hacia
main()), esos datos se recibirn en forma de cadena. El programa tendr que
convertirlos en el formato numrico apropiado utilizando las funciones de
biblioteca adecuadas. Describir las funciones ms comunes, sus prototipos de
funcin y el archivo de cabecera utilizado.
14) Qu hace el siguiente programa?.
#include <stdio.h>
#include <stdlib.h>
void main(int argc, char *argv[v])
{
int i ;
double d ;
long l ;
i = atoi( argv[1] ) ;
d = atof( argv[2] ) ;
l = atol( argv[3] ) ;
printf("%d %ld %lf", i, l, d) ;
}
Cap.6-30
CAPTULO 7
Ficheros
Captulo 7: Ficheros ProgramacinenC
7.1 INTRODUCCIN
En los ejemplos que se han expuesto hasta ahora, la entrada de datos y
la salida de resultados se han hecho a travs de la consola. Esto es til en
trabajos que manipulan pequeas cantidades de datos sobre todo, si stos
cambian de una ejecucin a otra.
Si la cantidad de datos a manipular es grande y adems muchos de ellos
son datos fijos de una ejecucin a otra, la consola no es el medio ms
adecuado, ya que adems de perder mucho tiempo introduciendo datos, esta
entrada tendra que efectuarse de nuevo para la siguiente ejecucin, debido
a que los datos no fueron almacenados en ningn soporte.
En estos casos es preciso utilizar soportes magnticos donde se
conserven grabados los datos que un determinado programa necesita cada vez que
se ejecuta.
Un fichero es una coleccin de informacin que se almacena en un
soporte, generalmente un disco magntico (disco duro o flexible), para poder
manipularla en cualquier momento. Esta informacin se almacena como un
conjunto de registros, conteniendo todos ellos, generalmente, los mismos
campos.
Este hecho plantea la siguiente pregunta: Qu tcnicas utiliza C para
almacenar la informacin y poderla recuperar en cualquier momento?. La
respuesta es que el lenguaje C ofrece el acceso a los ficheros por medio de
funciones de distinto nivel:
. A nivel de registro (record)
. A bajo nivel (tambin denominado secuencial)
Por registro se entiende un conjunto de datos (campos) simples o
compuestos de diferente tipo. Por ejemplo, un fichero puede contener la
descripcin de varias personas; cada persona representa para el fichero un
dato (registro) que, a su vez, puede estar constituido por otros datos
(campos) como: nombre, domicilio, edad, telfono, fecha de nacimiento, empresa
donde trabaja, etc.
A bajo nivel se considera un fichero como un conjunto de octetos, sin
tener en cuenta como se haya grabado, lo que por otra parte, tampoco se
materializa fsicamente en lo escrito en disco. Esto quiere decir que puede
leer un nmero de caracteres que se desee a partir de cualquier posicin en
el fichero (sea o no el comienzo de un sector de grabacin en disco). Por esta
razn es bastante posible que se obtengan caracteres que pertenezcan a uno de
los registros y caracteres que pertenezcan al siguiente.
Las operaciones a bajo nivel son las ms potentes, y siempre que se
respete la limitacin de leer cada vez un nmero de caracteres, igual al
tamao del registro, ofrece indudables ventajas sobre las operaciones a ms
alto nivel (nivel de registro).
Las operaciones a bajo nivel se puede decir que son algo as como las
"primitivas" del sistema: son las operaciones bsicas. Las operaciones a nivel
de registro se construyen a partir de las primitivas.
7.2 CORRIENTES DE TEXTO Y CORRIENTES BINARIAS
El sistema de E/S de C proporciona a los programadores una interfaz
consistente, que es independiente del dispositivo real al que se accede; es
decir, el sistema de E/S proporciona un nivel de abstraccin entre el
programador y el dispositivo que se usa. Esta abstraccin se llama corriente
Cap.7-2
Captulo 7: Ficheros ProgramacinenC
y el dispositivo real se llama archivo
1
.
El sistema de almacenamiento temporal en archivo est diseado para
trabajar con una amplia variedad de dispositivos, incluyendo terminales,
dispositivos de disco y unidades de cinta. Incluso aunque cada dispositivo
fuera diferente, el sistema de almacenamiento temporal en archivo los
transforma en un dispositivo lgico llamado corriente. Todas las corrientes
son similares en su comportamiento. Debido a que las corriente son muy
independientes del dispositivo, las mismas funciones que escriben en un
archivo de disco puede hacerlo en consola (redireccionamiento de salida). Hay
dos tipos de corrientes:
- Corriente de texto
- Corriente binaria
CORRIENTES DE TEXTO
Una corriente de texto es una secuencia de caracteres organizadas en
LNEAS terminadas por el carcter de nueva lnea. El estndar ANSI propuesto
establece el carcter de nueva lnea como opcional, dependiendo de la
implementacin. En una corriente de texto, pueden ocurrir ciertas traducciones
de caracteres segn precise el entorno del sistema central. Por ejemplo, una
nueva lnea se puede convertir en un par retorno de carro - nueva lnea. Esto
es lo que hacen la mayora de compiladores (entre ellos Turbo C). Por tanto,
puede no haber una relacin uno a uno entre los caracteres que se escriben
(leen) y los del dispositivo externo. Tambin, debido a las posibles
traducciones, el nmero de caracteres escritos (ledos) puede no ser el mismo
encontrado en el dispositivo externo.
CORRIENTES BINARIAS
Una corriente binaria es una secuencia de bytes que tienen una
correspondencia uno a uno con un dispositivo externo. As que no tendr lugar
ninguna traduccin de caracteres. Adems, el nmero de bytes escritos (ledos)
ser el mismo que los encontrados en el dispositivo externo. Sin embargo, el
estndar ANSI propuesto especifica que una corriente binaria puede tener un
nmero definido por la implementacin de bytes nulos que se aaden al final.
Por ejemplo, el ordenador podra usar estos bytes nulos para rellenar la
informacin de manera que complete un sector en el disco.
7.3 TRATAMIENTO DE FICHERO A NIVEL DE REGISTRO
Tambin se conoce con el nombre de sistema de memoria intermedia de E/S
porque se considera que utiliza una memoria de tipo tampn (baja capacidad de
almacenamiento y elevada velocidad de trabajo).
Cuando se hace intervenir la memoria tampn los caracteres no se
transmiten directamente al fichero (o al perifrico): se guardan temporalmente
en un "tampn" y solamente se transmiten cuando esta zona de memoria ya est
llena; de estas operaciones de almacenamiento y transmisin se encarga
automticamente el sistema.
Para realizar el acceso a un fichero se dispone de diversas funciones
relacionadas. Estas funciones requieren que se incluya el archivo cabecera
stdio.h en cualquier programa en el que se usen.
1
N. del A. Ntese la diferencia entre los trminos archivo: elemento real (impresora, puerto serie,
consola, unidad de disco, etc.) y fichero: nombre que recibe un conjunto de datos almacenados en una memoria
secundaria.
Cap.7-3
Captulo 7: Ficheros ProgramacinenC
Nombre Funcin
fopen() Abre una corriente
fclose() Cierra una corriente
putc() Escribe un carcter en una corriente
getc() Lee un carcter desde una corriente
fseek() Salta al byte especificado en una corriente
fprintf() Es la corriente lo que printf() a la consola
fscanf() Es la corriente lo que scanf() a la consola
feof() Devuelve verdad si se alcanza la marca EOF
ferror() Devuelve verdad si ha ocurrido un error
rewind() Seala la posicin del primer registro
remove() Borra un archivo
exit() Sale del programa cerrando todos los archivos abiertos
Las funciones ms comunes del sistema de archivo con memoria intermedia.
PUNTERO DE FICHERO
El hilo conductor de los ficheros tratados a nivel de registro es el
puntero de fichero. Es un puntero a la informacin que define varios aspectos
del fichero, incluyendo su nombre, estado y posicin actual. En esencia, el
puntero de fichero identifica un fichero de disco y se usa por su corriente
asociada para dirigir cada una de las funciones de memoria intermedia de E/S
al lugar donde se realizan las operaciones. Un puntero de fichero es una
variable de tipo FILE, que se define en stdio.h. Con objeto de leer o escribir
archivos, el programa necesita utilizar punteros de ficheros. Para obtener una
variable de un puntero de fichero, utilizar una sentencia como la siguiente:
FILE *puntero_fichero ;
TIPOS DE ORGANIZACIN DE ARCHIVOS EN -C-
Se denomina organizacin de archivos al modo o manera en que se
encuentran estructurados los archivos sobre el soporte. La estructura en
cuestin es el resultado de aplicar ciertas reglas para determinar la
disposicin y caractersticas de los correspondientes registros sobre los
dispositivos de almacenamiento.
La estructura depende tambin, en gran parte, de las pecualiaridades del
soporte utilizado.
Entre las caractersticas de la estructuracin se debe sealar, por su
importancia el modo de acceso al fichero. Se llama acceso al procedimiento
necesario para situarse en un registro determinado con el objeto de realizar
una operacin de lectura o escritura sobre el mismo. El lenguaje C proporciona
dos tipo de acceso:
A) Acceso Secuencial
B) Acceso Aleatorio
El acceso secuencial es el modo de trabajo por defecto, y consiste en
llegar a un registro (o dato) determinado pasando necesariamente por todos los
anteriores partiendo del primero.
El acceso aleatorio permite el posicionamiento directo sobre un dato
deseado.
7.4 OPERACIONES SOBRE FICHEROS A NIVEL DE REGISTRO
Las acciones que pueden realizarse sobre un fichero podran resumirse
como:
Cap.7-4
Captulo 7: Ficheros ProgramacinenC
- Apertura de un fichero
. Creacin
. Abierto en modo lectura
. Abierto en modo escritura
. Abierto en modo lectura/escritura
. Abierto para aadir informacin
La funcin utilizada es fopen().
- Escritura sobre un fichero
. Escritura de un carcter -> putc()
. Escritura de un entero -> putw()
. Escritura de una cadena -> fputs()
. Escritura de un bloque de datos -> fwrite()
. Escritura de varios datos (registro) -> fprintf()
- Lectura sobre un fichero
. Lectura de un carcter -> getc()
. Lectura de un entero -> getw()
. Lectura de una cadena -> fgets()
. Lectura de un bloque de datos -> fread()
. Lectura de diferentes tipos de datos -> fscanf()
- Borrado de un fichero
La funcin utilizada es remove()
- Acceso directo (aleatorio)
La funcin utilizada es fseek()
- Cierre de un fichero
La funcin utilizada es fclose()
- Utilidades
. Determinar si una operacin sobre fichero ha sido errnea
ferror()
. Reposicionar el fichero sobre el primer registro
rewind()
. Salida del programa en caso de no poder realizar una opoeracin
sobre un fichero
exit() - ver apertura de un fichero -
En los apartados siguientes se estudia la declaracin y forma de uso de
las funciones antes mencionadas, segn el tipo de operacin que realizan.
APERTURA DE UN FICHERO
La funcin fopen() sirve para dos propsitos:
A) Abre una corriente para usar y enlaza un fichero con una corriente.
B) Devuelve el puntero de fichero asociado con un archivo de disco.
Cap.7-5
Captulo 7: Ficheros ProgramacinenC
La funcin fopen() se declara como:
FILE *fopen(char *nombre_de_archivo, char *mode) ;
Aqu mode es una cadena que contiene el estado deseado de apertura. El
nombre_de_archivo debe ser una cadena de caracteres que es un nombre de
archivo para el sistema operativo, incluido el path si no se encuentra en el
directorio activo.
Los valores permitidos de mode en Turbo C quedan presentados en la
siguiente pantalla:
Modo Significado
"r" Abre un archivo de texto para lectura
"rt" Abre un archivo de texto para lectura
"rb" Abre un archivo binario para lectura
"w" Crea un archivo de texto para escritura
"wt" Crea un archivo de texto para escritura
"wb" Crea un archivo binario para escritura
"a" Aade a un archivo de texto
"at" Aade a un archivo de texto
"ab" Aade a un archivo binario
"r+" Abre un archivo para leer/escribir
"r+t" Abre un archivo de texto para leer/escribir
"r+b" Abre un archivo binario para leer/escribir
"w+" Crea un archivo para leer/escribir
"w+t" Crea un archivo de texto para leer/escribir
"w+b" Crea un archivo binario para leer/escribir
"a+" Aade o crea un archivo para leer/escribir
"a+t" Aade o crea un archivo de texto para leer/escribir
"a+b" Aade o crea un archivo binario para leer/escribir
Valores permitidos por mode
Si no se especifica la "t" de texto o la "b" de binario, el archivo se
abre conforme al valor de la variable global de Turbo C _fmode. Esta variable
establece o bien O_TEXT para modo texto, o bien O_BINARY para modo binario.
Por defecto es O_TEXT. Las macros O_BINARY y O_TEXT se encuentran en el
archivo fcntl.h.
En modo texto se traducen las secuencias de retorno de carro /
alimentacin de lnea en caracteres de nueva lnea en la entrada. En la
salida, ocurre lo contrario: las nuevas lneas se traducen en retorno de
carro/alimentacin de lnea. En los archivos binarios no se producen tales
traducciones.
Como se ha dicho anteriormente, la funcin fopen() devuelve un puntero
de fichero. El programa no debera alterar nunca el valor de este puntero,
pero si cuando se trata de abrir un archivo, aparece un error, fopen()
devuelve un nulo.
Para abrir un archivo de escritura con el nombre fichero_1, se
escribira:
FILE *puntero_fil ;
puntero_fil = fopen("fichero_1", "w") ;
Cap.7-6
Captulo 7: Ficheros ProgramacinenC
Sin embargo usualmente se ver el cdigo escrito como esto:
FILE *puntero_fil ;
if((puntero_fin = fopen("fichero_1","w")) == NULL)
{
puts("no puede abrirse el fichero\n") ;
exit(1) ;
}
La macro NULL recordar que est definida en stdio.h. Este mtodo detecta
cualquier error al abrir un fichero: como por ejemplo un disco lleno o
protegido contra escritura antes de comenzar a escribir en l. Se utiliza nulo
porque ningn puntero de archivo tendr nunca ese valor.
Si se usa fopen() para abrir un archivo para escritura, entonces
cualquier archivo ya existente con el mismo nombre se borrar y se crea uno
nuevo. Si no existe un archivo con el mismo nombre, entonces se crear. Si se
quiere aadir al final del archivo, entonces se debe usar el modo a. Si se usa
a y no existe el archivo, se devolver un error. La apertura de un archivo
para las operaciones de lectura requiere que exista el archivo. Si no existe,
fopen() devolver un error. Finalmente, si se abre un archivo para las
operaciones de leer/escribir, el ordenador no lo borrar si existe; sin
embargo, si no existe, la computadora lo crear.
Un aspecto importante que no debe olvidarse es la respuesta del programa
cuando una operacin de apertura no puede producirse satisfactoriamente. En
tal caso, la opcin ms utilizada por los programadores consiste en salir al
sistema operativo; este hecho no debe realizarse de cualquier manera, puesto
que podra darse el caso de deteriorar irreversiblemente los ficheros que
hasta el momento estuvieran abiertos. El proceso habitual es llamar a la
funcin exit() con la cual se indica la inmediata salida del programa y ADEMS
el cierre automtico de aquellos ficheros que permanecieran abiertos. Tambin
puede devolver un valor especfico que el programador indicar en el argumento
de llamada.
Su prototipo de funcin es:
void exit(int status) ; /* en <stdlib> y <process> */
ESCRITURA DE UN FICHERO
Para llevar informacin hacia un fichero, C ofrece diferentes rutinas
que dependen del tipo de dato a guardar:
Escribir un carcter
Se usa la funcin putc() para escribir caracteres sobre un fichero
abierto por fopen() (en un modo que permita la escritura). La funcin se
declara como:
int putc(int ch, FILE *fp) ; /* prototipo en <stdio.h> */
Donde fp es el puntero de archivo devuelto por fopen() y ch es el
carcter que se escribe. El puntero de archivo le dice a putc() sobre qu
archivo de disco debe escribir. Por razones histricas, ch se llama
formalmente int, pero el ordenador slo usa el byte de orden ms bajo.
Escribir un entero
Similar a la funcin anterior pero en vez de escribir un byte
(carcter), guarda un valor entero (dos bytes). Su prototipo de funcin es:
int putw(int i, FILE *fp) ; /* prototipo en <stdio.h> */
Cap.7-7
Captulo 7: Ficheros ProgramacinenC
Escribir una cadena de caracteres
La funcin fputs() funciona como puts(), excepto que fputs() escribe la
cadena sobre el fichero indicado. Su declaracin es:
char *fputs(char *cadena, FILE *fp) ; /* prototipo en <stdio.h> */
Escribir un bloque de datos
El formato general de esta declaracin es:
unsigned fwrite(void *mem_intermedia,int num_bytes,int cont,FILE *fp);
Para la funcin fwrite() mem_intermedia es un puntero a la informacin
que se escribir en el archivo. Con num_bytes se indica el nmero de bytes que
se van a escribir. El argumento cont determina cuntos elementos (cada uno de
num_bytes de longitud) se escribirn. Finalmente, fp es un puntero de archivo
que seala a un fichero definido previamente. Su prototipo se encuentra en
<stdio.h>.
Por ejemplo, este programa escribe un float en un archivo de disco:
/* Escribe en disco un fichero (test) con un n en coma flot. */
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp ;
float f = 12.23 ;
if ((fp = fopen("test","wb")) == NULL)
{
printf("no puede abrir el archivo\n") ;
exit(1) ;
}
fwrite(&f, sizeof(float), 1, fp) ;
fclose(fp) ;
}
Como ilustra el programa, la memoria intermedia puede ser, y
frecuentemente lo es, una sencilla variable. Una de las aplicaciones ms
tiles de fwrite() es la escritura de tablas o estructuras. Por ejemplo el
siguiente fragmento escribe los contenidos de la tabla de punto flotante
balance al archivo muestra.
Cap.7-8
Captulo 7: Ficheros ProgramacinenC
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp ;
float muestra[100] ;
int i ;
if((fp = fopen ("muestra","wb")) == NULL)
{
printf("no puedo abrir el archivo \n") ;
exit(1) ;
}
for(i = 0 ; i < 100 ; i++)
muestra[i] = (float) i ;
/* graba la tabla muestra en un paso */
fwrite(muestra, sizeof(muestra), 1, fp) ;
fclose(fp) ;
}
Escribir varios tipos de datos
La funcin fprintf() se comporta igual que printf(), excepto que
funciona con archivos de disco. El formato general de esta declaracin es:
int fprintf(FILE *fp, char *control_cadena, lista_argumentos) ;
Siendo fp un puntero de archivo que se devuelve al llamar a fopen().
Un pequeo ejemplo podra ser:
#include <stdio.h>
main()
{
FILE *MiAuto ;
/* comprueba si existe el fichero autoexec.bat */
MiAuto = fopen("c:\AUTOEXEC.BAT","a") ;
if(MiAuto == NULL)
{
printf("Error al abrir el autoexec.bat\n") ;
exit (1) ;
}
fprintf(MiAuto, "PATH = C:\TC", "%PATH%\n") ;
fclose(MiAuto) ;
}
/* escribir c:set para averiguar si se modific el PATH */
La llamada a fprintf() devuelve el nmero de caracteres escritos en el
archivo. En caso de que se produzca un error al intentar acceder al mismo, se
Cap.7-9
Captulo 7: Ficheros ProgramacinenC
retorna con un nmero negativo.
LECTURA DE UN FICHERO
Para tomar la informacin almacenada en un fichero y enviarla, por
ejemplo, hacia la consola, C aporta varias funciones que se deben utilizar
segn el tamao de la informacin:
Lectura de un carcter
La funcin getc() se usa para leer caracteres desde el fichero que
fopen() ha abierto en el modo lectura. La funcin se declara como:
int getc(FILE *fp) ;
El objeto fp es un puntero de tipo FILE que se devuelve por fopen().
Recordar que, por razones histricas, getc() devuelve un entero, pero el byte
ms alto es cero.
La funcin getc() retorna con una marca EOF (End Of File) cuando el
ordenador alcanza el final del archivo. Por tanto, para leer un archivo de
TEXTO hasta la marca de final de archivo, se podra usar el siguiente cdigo:
ch = getc(fp) ;
while(ch != EOF)
{
...
ch = getc(fp) ;
}
USO DE LA FUNCIN feof()
Como se estableci anteriormente, el sistema de archivo como
almacenamiento intermedio puede funcionar tambin con datos binarios. Cuando
se abre un archivo para entrada binaria, es posible que el ordenador pueda
leer un valor entero que sea igual a la marca EOF. Si esto sucede, causara
que la rutina lo d precisamente para indicar una condicin de fin de archivo
fsico. Para resolver este problema, La librera estndar incluye la funcin
feof(), que determina dnde est la marca de fin de archivo cuando se leen los
datos binarios. La funcin feof() tiene el prototipo:
int feof(FILE *fp) ;
El cual est en <stdio.h>. Es verdad si se ha llegado al final del
archivo; si no sera cero. Por tanto, esta rutina lee un archivo binario hasta
que el ordenador encuentra la marca de final de archivo.
while(!feof(fp))
ch = getc(fp) ;
Se puede aplicar este mismo mtodo para los archivos de texto tal como
a los binarios.
Un ejemplo donde se utilizan las funciones del lectura y escritura de
caracteres sobre un fichero (getc() y putc() respectivamente) es este micro-
editor:
Cap.7-10
Captulo 7: Ficheros ProgramacinenC
/* micro editor de texto */
#include <stdio.h>
main()
{
FILE *fp ;
int c ;
printf("Introducir un texto (finaliza con -^Z- <F6>)\n") ;
fp = fopen("temporal", "w") ;
if(fp == NULL)
{
printf("Error sobre el archivo -temporal-\n") ;
exit(1) ;
}
/* Este grupo de sentencias*/
while((c = getchar()) != EOF) /* representan la parte de */
/* introduccin de datos en*/
putc(c, fp) ; /* el fichero de datos: */
/* */
fclose(fp) ; /* temporal */
/*--------------------------------------------------------------*/
fp = fopen("temporal", "r") ;
if(fp == NULL)
{
printf("Error sobre el archivo -temporal-\n") ;
exit(1) ;
}
/* Este grupo de sentencias*/
while((c = getc(fp)) != EOF) /* representan la parte de */
/* obtencin de datos desde*/
putchar(c) ; /* el fichero de datos: */
/* */
fclose(fp) ; /* temporal */
}
Cap.7-11
Captulo 7: Ficheros ProgramacinenC
El siguiente ejercicio de ejemplo permite leer cualquier archivo ASCII
y visualizar el contenido en pantalla:
/* datos: Programa que lee archivos y los visualiza en pantalla */
#include <stdio.h>
#include <stdlib.h>
main( int argc, char *argv[] )
{
FILE *fp ;
char ch ;
if( argc != 2)
{
printf("Debe introducir nombre de archivo\n") ;
exit(1) ;
}
if((fp = fopen(argv[1], "r")) == NULL)
{
printf("no puede abrir el archivo\n") ;
exit(1) ;
}
ch = getc(fp) ; /* lee un carcter */
while ( ch != EOF)
{
putchar(ch) ;
ch = getc(fp) ;
}
fclose(fp) ;
}
Lectura de enteros
La funcin getw() es similar a la funcin anterior. Se utiliza para leer
enteros (2 bytes) desde un fichero. Su prototipo de funcin es:
int getw(FILE *fp) ; /* su prototipo est en stdio.h */
Lectura de una cadena
Para la lectura de cadenas en un fichero existe una funcin denominada
fgets(). Su formato de declaracin es:
char *fgets(char *cadena, int long, FILE *fp) ;
La funcin fgets() lee una cadena desde el fichero especificado hasta
que lee un carcter de nueva lnea o longitud - 1 caracteres. Si fgets() lee
una carcter de nueva lnea, ser parte de la cadena (al contrario que
gets()). Sin embargo, cuando se termina fgets(), la cadena resultante ser
terminada en un finalizador nulo.
Su prototipo est en <stdio.h>.
Modificando el programa anterior se puede adaptar para recoger las
funciones de cadenas en el tratamiento de archivo:
Cap.7-12
Captulo 7: Ficheros ProgramacinenC
/* datos2: Programa que lee y visualiza archivos */
/* utiliza la funcin de biblioteca fgets */
#include <stdio.h>
#include <stdlib.h>
main( int argc, char *argv[] )
{
FILE *fp ;
char cadena[100] ;
if( argc != 2)
{
printf("Debe introducir nombre de archivo\n") ;
exit(1) ;
}
if((fp = fopen(argv[1], "r")) == NULL)
{
printf("no puede abrir el archivo\n") ;
exit(1) ;
}
while ( fgets(cadena, 100, fp) != NULL) /* lee fichero */
printf(cadena) ;
fclose(fp) ;
}
Lectura de un bloque
La funcin fread() permite leer bloques de datos de un fichero. El
formato general de la declaracin es:
unsigned fread(void *mem_intermedia,int num_bytes,int cont,FILE *fp) ;
El trmino mem_intermedia es un puntero a una zona de la memoria que
recibir los datos ledos desde el archivo. Igual que en la funcin fwrite(),
el resto de los argumentos especifican:
- num_bytes nmero de bytes para leer.
- cont nmero de elementos (con una longitud por elemento de num_bytes).
- fp puntero de archivo sealando a un fichero.
Su prototipo de funcin est definido en <stdio.h>.
Cap.7-13
Captulo 7: Ficheros ProgramacinenC
#include <stdio.h>
main()
{
FILE *fp ;
struct
{
char nom[18];
int edad ;
}
persona ;
char buf[65] ; /* Tampn de lectura para el teclado */
if ((fp = fopen("nombre.dat", "wb")) == NULL)
{
printf("No se puede crear <nombre-dat>\n" ) ;
exit(1) ;
}
printf("\nIntroduzca nombre <Enter finalizar>: ") ;
gets(buf) ;
while(buf[0]) /* Si no hay carcter buf[0] == 0x00 */
{
strcpy(persona.nom, buf) ;
printf("\nEDAD: ") ;
gets(buf) ;
sscanf(buf, "%d", &persona.edad) ;
/* sscanf() cambia el formato de una cadena "123"
en su equivalente segn el carcter de formato
(en -%d- decimal sera: 123) */
fwrite((char *) &persona, sizeof(persona), 1, fp) ;
printf("\nIntroduzca nombre <Enter finalizar>: ") ;
gets(buf) ;
}
fclose(fp) ;
if((fp = fopen("nombre.dat", "rb")) == NULL)
{
printf("\no puedo abrir <nombre.dat> para leerlo") ;
exit(1) ;
}
/* recorrido secuencial del fichero */
while(fread((char *) &persona, sizeof(persona), 1, fp))
printf("%s (%d aos) \n", persona.nom, persona.edad) ;
fclose(fp) ;
}
Cuando el archivo est abierto para datos binarios, fread() y fwrite()
pueden trabajar con cualquier tipo de informacin.
El siguiente programa utiliza fread() para visualizar la informacin
entregada a una tabla de cifras en coma flotante (ver ejercicio demostrativo
de la funcin fwrite):
Cap.7-14
Captulo 7: Ficheros ProgramacinenC
#include <stdio.h>
#include <stdlib.h>
main()
{
FILE *fp ;
float tabla[100] ;
int i ;
if((fp = fopen("muestra", "rb")) == NULL)
{
printf("no puede abrirse el archivo\n") ;
exit(1) ;
}
/* lee la tabla de elementos en un paso */
fread(tabla, sizeof(tabla), 1, fp) ;
for(i = 0 ; i < 100 ; i ++)
printf("%f ", tabla[i]) ;
fclose(fp) ;
}
Lectura de varios tipos
La funcin fscanf() tiene un comportamiento similar a scanf(), con la
nica diferencia de estar orientada a archivos de disco. El formato general
de la declaracin de fscanf() es:
int fscanf(FILE *fp, char cadena_control, lista_argumentos) ;
Donde fp es el puntero del archivo utilizado.
Para ilustrar lo utilsimas que estas funciones (fprintf() y fscanf())
pueden ser, el programa siguiente mantiene un sencillo directorio telefnico
en un archivo de disco. Se pueden introducir nombre y nmeros o se puede
bustcar un nmero que corresponda al nombre en el que se est interesado.
Cap.7-15
Captulo 7: Ficheros ProgramacinenC
/* directorio de telfonos */
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
void suma_num(void), mirar (void) ; /* prototipo de funcin */
char menu(void) ; /* prototipo de funcin */
main()
{
char elige ;
clrscr() ;
do
{
elige = menu() ;
switch(elige)
{
case a: suma_num() ;
break ;
case m: mirar() ;
break ;
}
}
while(elige != s) ;
}
/* ----- vislualiza el men y espera la respuesta ----- */
char menu(void)
{
char ch ;
do
{
printf("A) aadir, M) mirar, S) salir: ") ;
ch = tolower(getche()) ;
printf("\n") ;
}
while(ch != s && ch != a && ch != m) ;
return ch ;
}
/* ----- Aadir un nombre y nmero al directorio ----- */
void suma_num(void)
{
FILE *fp ;
char nombre[80] ;
unsigned long int num ;
if((fp = fopen("telefono","a")) == NULL)
{
printf("no puede abrir archivo -telefono-\n") ;
exit(1) ;
}
printf("introducir nombre y nmero: ") ;
fscanf(stdin, "%s%lu", nombre, &num) ;
fscanf(stdin, "%*c") ;/* quita CR de la corriente de ent*/
/* ver -corrientes estndar- */
fprintf(fp, "%s %lu\n", nombre, num) ;
fclose(fp) ;
}
/* ----- Encuentra un nmero dando un nombre ----- */
void mirar(void)
{
FILE *fp ;
char nombre[80], nombre2[80] ;
unsigned long int num ;
if((fp = fopen("telefono","r")) == NULL)
Cap.7-16
Captulo 7: Ficheros ProgramacinenC
{
printf("no puedo abrir <telefono>\n") ;
exit(1) ;
}
printf("Introduzca nombre: ") ;
gets(nombre2) ;
while(!feof(fp)) /* mira un nmero */
{
fscanf(fp,"%s%lu", nombre, &num) ;
if(!strcmp(nombre, nombre2) && !feof(fp))
{
printf("%s: (%lu)\n", nombre, num) ;
}
}
fclose(fp) ;
}
BORRADO DE UN FICHERO
La funcin remove() borra un archivo que se especifique. El formato
general de esta declaracin es:
int remove(char *nombre_de_archivo) ;
Si funciona correctamente, remove() devuelve cero; si no, un valor
distinto de cero.
ACCESO DIRECTO (ALEATORIO) SOBRE UN FICHERO
Se pueden realizar las operaciones de lectura y escritura directas bajo
el sistema de memoria intermedia de E/S con la ayuda de la funcin fseek(),
que establece el localizador de la posicin del archivo. El formato general
de la declaracin de fseek() es:
int fseek(FILE *fp, long num_bytes, int origen) ;
Donde fp es un puntero de archivo que se devuelve en una llamada a
fopen(); num_bytes, definido como entero long, es el nmero de bytes desde el
origen a la nueva posicin y origen es una de las siguientes MACROS definidas
en <stdio.h>:
Origen Nombre Valor real
Comienzo de archivo SEEK_SET 0
Posicin actual SEEK_CUR 1
Fin de archivo SEEK_END 2
Por tanto, para establecer el num_bytes desde el comienzo del archivo,
origen sera SEEK_SET. Para buscar el num_bytes desde la posicin actual,
origen sera SEEK_CUR; y desde el final del archivo sera SEEK_END.
Recordar que num_bytes debe ser un long int para soportar operaciones
sobre archivo que son mayores que 64K bytes.
Cap.7-17
Captulo 7: Ficheros ProgramacinenC
El siguiente fragmento lee 235 bytes en un archivo llamado TEST.
...
FILE *fp ;
char ch ;
if((fp = fopen("TEST", "rb")) == NULL)
{
printf("no puede abrirse el archivo\n") ;
exit(1) ;
}
fseek(fp, 234L, 0) ;
ch = getc(fp) ; /* lee un carcter en la posicin 235 */
...
Obsrvese que este cdigo aade el modificador L a la constante 234 para
forzar al compilador a tratar la constante como un long. Se podra usar
tambin una mscara (cast) para conseguir el mismo resultado. (Recurdese que
tratar de usar un entero normal provocar errores cuando el ordenador espera
un entero long).
Si se devuelve un cero, es que funcion correctamente fseek(). Un valor
distinto de cero indica un error.
Un ejemplo ms interesante es el programa DUMP mostrado a continuacin,
que usa la funcin fseek() para permitir al usuario examinar los contenidos
ASCII y hexadecimal de cualquier archivo que se indique. Se puede ver el
archivo en <sectores> de 128 bytes. El estilo de la salida visualizada es
similar al formato usado por el programa del DOS -DEBUG- cuando se aplica la
orden "D" (dump -volcado de memoria). Se sale del programa tecleando -1 cuando
pregunta por el sector.
En el programa debe fijarse en el uso de fread() para leer el archivo.
Recordar que fread() devuelve el nmero de elementos ledos realmente.
/* DUMP: utilidad de bsqueda en disco usando fseek */
#include <stdio.h>
#include <ctype.h> /* utilizado para isprint() */
#include <stdlib.h> /* utilizado para exit() */
#define SIZE 128
void display(int numread) ;
char buf[SIZE] ;
main(int argc, char *argv[])
{
FILE *fp ;
int numread ;
long int sector ;
if(argc !=2) /*indicar correctamente el n de argumentos*/
{
printf("la llamada es: dump nom_archivo\n") ;
exit(1) ;
}
if((fp = fopen(argv[1], "rb")) == NULL)
{
Cap.7-18
Captulo 7: Ficheros ProgramacinenC
printf("no puede abrirse el archivo \n") ;
exit(1) ;
}
printf("introduce sector (-1 sale): ") ;
scanf("%ld", &sector) ;
do
{
if(fseek(fp, sector * SIZE, SEEK_SET))
printf("error de acceso directo\n") ;
if((numread = fread(buf, 1, SIZE, fp)) != SIZE)
printf("se ha alcanzado EOF\n") ;
display(numread) ;
printf("introduce sector (-1 sale): ") ;
scanf("%ld", &sector) ;
}
while(sector >= 0) ;
}
void display(int numread) /* visualiza el fichero */
{
int i, j ;
for(i = 0 ; i < numread / 16 ; i++)
{
for(j = 0 ; j < 16 ; j++) /* imprime valor Hex. */
printf("%3x", buf[i * 16 + j]) ;
printf(" ") ;
for(j = 0 ; j < 16 ; j++) /* imprime valor ASCII */
if(isprint(buf[i * 16 + j]))
printf("%c", buf[i * 16 + j]) ;
else
printf(".") ;
printf("\n") ;
}
}
El programa DUMP introduce el uso de una funcin de biblioteca llamada
isprint(), que determina qu caracteres son imprimibles y cules no. La
funcin isprint() devuelve verdad si el carcter es imprimible y falso en caso
contrario. La funcin isprint() requiere el archivo de cabecera <ctype.h>. Un
ejemplo de salida con DUMP usado sobre s mismo se muestra a continuacin:
Cap.7-19
Captulo 7: Ficheros ProgramacinenC
introduce sector (-1 sale): 1
20 70 61 72 61 20 69 73 70 72 69 6e 74 28 29 20 para isprint()
2a 2f d a 20 20 20 20 20 20 23 69 6e 63 6c 75 */.. #inclu
64 65 20 3c 73 74 64 6c 69 62 2e 68 3e 20 2f 2a de <stdlib.h> /*
20 75 74 69 6c 69 7a 61 64 6f 20 70 61 72 61 20 utilizado para
65 78 69 74 28 29 20 2a 2f d a d a 20 20 20 exit() */....
20 20 20 23 64 65 66 69 6e 65 20 53 49 5a 45 20 #define SIZE
31 32 38 d a d a 20 20 20 20 20 20 76 6f 69 128.... voi
64 20 64 69 73 70 6c 61 79 28 69 6e 74 20 6e 75 d display(int nu
introduce sector (-1 sale): 2
6d 72 65 61 64 29 20 3b d a d a 20 20 20 20 mread) ;....
20 20 63 68 61 72 20 62 75 66 5b 53 49 5a 45 5d char buf[SIZE]
20 3b d a d a 20 20 20 20 20 20 6d 61 69 6e ;.... main
28 69 6e 74 20 61 72 67 63 2c 20 63 68 61 72 20 (int argc, char
2a 61 72 67 76 5b 5d 29 d a 20 20 20 20 20 20 *argv[])..
7b d a 20 20 20 20 20 20 20 20 20 20 20 20 20 {..
20 46 49 4c 45 20 2a 66 70 20 3b d a 20 20 20 FILE *fp ;..
20 20 20 20 20 20 20 20 20 20 20 69 6e 74 20 6e int n
introduce sector (-1 sale): -1
CIERRE DE UN FICHERO
Como se ha visto anteriormente la funcin fclose() se utiliza para
cerrar aquellos archivos que fueron archivos abierto con fopen(). Recurdese:
se deben cerrar todos los ficheros ANTES de que termine el programa. La
funcin fclose() escribe en el archivo los datos que todava quedan en la
memoria intermedia del disco y hace un cierre formal a nivel del sistema
operativo en el archivo. El fallo en este cierre invita a toda clase de
problemas, incluyendo prdida de datos, archivos destruidos y posibles errores
intermitentes en el programa. Usando fclose() se libera el bloque de control
de archivo y lo deja disponible para re-uso. Como se sabe, el sistema
operativo limita el nmero de archivos que puede tener abiertos en un momento,
as que se puede necesitar cerrar un archivo antes de abrir otro. La forma
general para declarar fclose() es:
int fclose(FILE *fp) ;
Donde fp es el puntero de archivo que se devuelve en la llamada a
fopen(). Un valor devuelto de cero significa el xito de la operacin de
cierre; cualquier otro indica un error. Se puede usar la funcin estndar
ferror() (discutido a continuacin) para determinar e informar de cualquier
problema. Generalmente, el nico momento en que fclose() fallar es, o cuando
se quita un disquete prematuramente de la unidad o cuando no hay ms espacio
en la unidad de almacenamiento.
UTILIDADES - ferror(), rewind() Y OTRAS FUNCIONES -
La funcin ferror() determina si una operacin con archivos produce un
error. El formato general para declarar ferror() es:
int ferror(FILE *fp) ;
Donde fp es el puntero de archivo que se devuelve en la llamada a
fopen(). La funcin ferror() retorna "cierto" si ha ocurrido un error durante
la ltima operacin de archivo; devuelve falso (0) si tuvo xito la operacin.
Como cada operacin de archivo establece la condicin de error, se debera
llamar inmediatamente a ferror() despus de cada operacin con archivo; si no
se puede perder un error. El prototipo de ferror() est en <stdio.h>.
La funcin rewind() reestablecer el localizador de posicin al comienzo
del archivo que se especifique como argumento. El formato general de la
declaracin es:
void rewind(FILE *fp) ;
Donde fp es un puntero vlido de archivo. El prototipo de rewind() est
en <stdio.h>.
Cap.7-20
Captulo 7: Ficheros ProgramacinenC
Otras funciones tiles proporcionadas por <stdio.h> (la biblioteca
estndar) son:
- ftell() .- Devuelve la posicin actual, expresada en octetos, a
partir del principio del fichero, o -1 en caso de error.
Su prototipo es: long ftell(FILE *fp) ;
- clearerr().- Pone a cero el indicador de error. Una vez activado,
permanecer as, haciendo que cualquier funcin sobre este
fichero devuelva EOF.
Su prototipo es: void clearerr(FILE *fp) ;
- fflush() .- Fuerza la escritura (fsica) en el disco de lo que haya
en la memoria tampn.
Su prototipo es: int fflush(FILE *fp) ;
Una aplicacin interesante de esta funcin es:
fflush(stdin) ;
Vaca el buffer que guarda la corriente de entrada (por
consola) en caso de entrada incorrecta.
En ocasiones al utilizarse la funcin scanf() (generalmente
dentro de una estructura repetitiva -for, while...-) puede
suceder que exista algn problema al adquirir datos desde
la consola porque la memoria intermedia, que alberga
temporalmente los datos, no queda completamente vaca tras
guardar en la variable correspondiente el ltimo valor
introducido por teclado. Para evitar este engorroso problema
se aplica la funcin fflush(), con el argumento stdin, que
seala el limpiado de la corriente de entrada (y no un
determinado fichero).
LAS CORRIENTES ESTNDAR
Siempre que un programa en C comienza su ejecucin, el ordenador abre
tres corrientes automticamente. Son:
- Entrada estndar (stdin)
- Salida estndar (stdout)
- Error estndar (stderr)
Normalmente, estas corrientes se refieren a la consola, pero el sistema
operativo puede redireccionarlas a algn otro dispositivo de corriente. Como
estos son punteros de archivo, se puede usar el sistema de E/S para realizar
las operaciones de E/S en la consola. Por ejemplo, se podra definir putchar()
como:
putchar(char c)
{
putc(c, stdout) ;
}
En general, se usa stdin para leer desde la consola, stdout para
escribir en la consola y stderr para escribir en la consola. Es posible usar
stdin, stdout y stderr como punteros de archivo en cualquier funcin que use
un puntero de archivo.
Cap.7-21
Captulo 7: Ficheros ProgramacinenC
Tener en cuenta que stdin, stdout y stderr no son variables sino
constantes y que no se pueden alterar. Tambin, en el momento en que el
ordenador crea estos punteros de archivos automticamente al comienzo del
programa, los cierra justo despus del final; no debera tratar de cerrarlos.
Por ejemplo al utilizar la siguiente sentencia se fuerza a que el
mensaje de error indicado sea presentado por pantalla.
fprintf(stderr, "Error al abrir el archivo %s\n", argv[1]) ;
Al comenzar la versin 1.5 de Turbo C, el cdigo tambin abre stdprn
(relacionado con la impresora) y stdaux (asociado al COM1 -auxiliar-).
Considerar este programa:
#include <stdio.h>
main()
{
printf("Esto es un ejemplo de redireccin") ;
}
Suponer que este programa se llama PRUEBA. Si se ejecuta PRUEBA
normalmente, muestra la cadena en la pantalla. Sin embargo, recordar que, si
un programa soporta redireccin de E/S, stdout se puede redireccionar a un
archivo. Por ejemplo, en un entorno DOS, OS/2, Windows, o UNIX, si se ejecuta
PRUEBA como:
PRUEBA > SALIDA
Hace que la salida PRUEBA se escriba en un archivo llamado SALIDA.
Recordar que en entornos que permiten redireccin de E/S, stdin y stdout
se pueden asociar a dispositivos distintos del teclado y la pantalla.
La entrada tambin se puede redireccionar. Por ejemplo, considerar este
programa:
#include <stdio.h>
main()
{
int i ;
scanf("%d", &i) ;
printf("%d", i) ;
}
Suponiendo que se llame PRUEBA, si se ejecuta como
PRUEBA < ENTRADA
Hace que se redireccione stdin al archivo llamado ENTRADA. Suponiendo
que ENTRADA contiene la representacin ASCII de un entero, el valor de este
entero se leer desde el archivo y se imprimir en la pantalla.
7.5 TRATAMIENTO DE FICHEROS A BAJO NIVEL
Como C se desarrollo originalmente bajo el sistema operativo UNIX, se
cre un segundo sistema de E/S de archivo en disco formado por unas funciones
que no utilizan almacenamiento intermedio, como en el caso anterior. El
programador debe proporcionar y mantener todas las memorias intermedias de
disco -las rutinas no las mantendrn-.
Cap.7-22
Captulo 7: Ficheros ProgramacinenC
Recordar que el estndar ANSI propuesto no define el sistema de archivo
sin almacenamiento intermedio. Esto implica que los programas que lo usan
tendern problemas de portabilidad en el futuro. Por tanto, se espera que el
uso del sistema de archivo a bajo nivel disminuir en los prximos aos. En
el momento de escribir esto, sin embargo, muchos programas existentes de C
usan el sistema, que se soporta en virtualmente todos los compiladores
existentes de C, incluyendo el Turbo C.
Los prototipos y la informacin relacionada necesaria para el uso del
sistema de archivos sin almacenamiento intermedio se encuentran en el archivo
de cabecera <io.h>.
Las funciones de archivo a bajo nivel son:
Nombre Funcin
creat() Crea un archivo
open() Abre un archivo de disco
read() Lee una memoria intermedia de datos
write() Escribe una memoria intermedia de datos
iseek() Salta a un byte concreto en un archivo
close() Cierra un archivo de disco
unlink() Quita un archivo del directorio
Funciones de la lnea UNIX sin almacenamiento intermedio
7.6 OPERACIONES SOBRE FICHEROS A BAJO NIVEL
Al contrario del sistema de alto nivel de E/S, el sistema a bajo nivel
no usa punteros de archivo tipo FILE, sino que usa descriptores de archivo
llamados manejadores de tipo int para identificar el archivo, en vez de
utilizar su nombre. Toda la informacin acerca de un archivo abierto es
mantenida por el sistema; el programa del usuario se refiere al archivo slo
por el descriptor.
CREACIN, APERTURA Y CIERRE DE FICHEROS A BAJO NIVEL
El formato general de la declaracin open() es:
int open(char *nombre_de_archivo, int modo, int acceso) ;
Donde nombre_de_archivo es cualquier nombre de archivo y modo es una de
las siguientes macros que se definen en <fcntl.h>:
MODO EFECTO VALOR REAL
O_RDONLY slo lectura 1
O_WRONLY slo escritura 2
O_RDWR lectura/escritura 4
El parmetro acceso slo se relaciona con los entornos de UNIX y se
incluye en la declaracin para compatibilidad. Turbo C tambin define una
versin especfica de DOS llamada _open(). El formato general de esta
declaracin es:
int _open(char *nombre_de_archivo, int modo) ;
Que salta el parmetro de acceso. En los ejemplos de este captulo,
acceso se pondr a cero.
Cap.7-23
Captulo 7: Ficheros ProgramacinenC
Una llamada correcta a open() devuelve un entero no negativo llamado
descriptor de archivo. El valor devuelto -1 significa que el archivo no se
puede abrir. Las otras funciones de sistemas de archivos sin almacenamiento
intermedio requieren un descriptor de archivo. El descriptor de archivos es
fundamentalmente distinto del puntero de archivo usado en el sistema estndar
ANSI de E/S.
...
int fd /* declaracin del descriptor de fichero */
...
if(fd = open(nombre_fichero, modo, 0)) == -1)
{
printf("no puedo abrir el archivo\n") ;
exit(1) ;
}
Si el archivo que se especifica en open() no aparece en el disco, la
operacin fallar y no crear el archivo.
El formato general de la declaracin de close() es:
int close(int fd) ;
Si close() devuelve -1, es que no pudo cerrar el archivo. Esto podra
ocurrir si se quita el disquete de la unidad, por ejemplo.
Una llamada a close() borra el descriptor del archivo de manera que se
puede rehusar otro archivo. Siempre hay algn lmite para el nmero de
archivos abiertos que pueden existir simultneamente, as que se debera usar
close() para cerrar un archivo cuando no se necesita ms. Ms importante, una
operacin de cierre fuerza al ordenador a escribir en disco cualquier
informacin de las memorias intermedias del disco del sistema operativo. Un
fallo en cerrar un archivo conduce usualmente a la prdida de los datos.
Se usar creat() para crear un archivo nuevo para operaciones de
escritura. El formato general de la declaracin de creat() es:
int creat(char *nombre_de_archivo, int acceso) ;
Donde nombre_de_archivo es cualquier nombre de archivo vlido. Se usa
el argumento acceso para especificar los modos de acceso y marcar el archivo
como binario de texto.
Por el uso de acceso en creat() relacionado con el entorno UNIX, el
compilador Turbo C proporciona una versin especial de MS-DOS llamada
_creat(), que toma el byte de atributo de archivo en vez del acceso. En DOS,
cada archivo tiene asociado un byte de atributo que especifica diversos bits
de informacin. En la siguiente tabla se presenta la organizacin de este
atributo de byte. Los valores de la tabla son aditivos; es decir, si se desea
crear un archivo oculto que es de slo lectura, se usara el valor 3 (1 + 2)
para acceso. Generalmente para crear un archivo estndar, acceso ser cero.
Bit Valor Significado
0 1 Archivo de solo lectura
1 2 Archivo oculto
2 4 Archivo del sistema
3 8 Nombre de etiqueta de volumen
4 16 Nombre de subdirectorio
5 32 Archivo
6 64 No usado
7 128 No usado
Organizacin del byte de atributos del DOS
Cap.7-24
Captulo 7: Ficheros ProgramacinenC
ESCRITURA Y LECTURA DE FICHEROS
Despus de abrir un archivo para escritura se puede acceder usando
write(). El formato general de la declaracin de la funcin write) es:
int write(int fd, void *memoria, int tamao) ;
Cada vez que el ordenador ejecuta una llamada a write(), escribe tamao
caracteres desde la memoria intermedia apuntada por memoria hasta el archivo
de disco que se especifica por fd.
La respuesta es que write() puede escribir una memoria intermedia
parcialmente. La funcin write() devuelve el nmero de bytes que escribi
durante una operacin de escritura completa. Cuando falla, write() devuelve
-1.
La funcin read() es complementaria a write(). El prototipo de su
declaracin es:
int read(int fd, void *memoria, int tamao) ;
En este caso, fd, memoria y tamao son los mismos de write(), excepto
que read() pondr el dato ledo en la memoria intermedia que apunta memoria.
Si acaba bien read(), devuelve el nmero de caracteres ledos realmente. Si
devuelve 0, es que se alcanza el final fsico del archivo; y si ocurren
errores, devuelve -1.
El programa mostrado a continuacin ilustra algunos aspectos del sistema
de E/S sin almacenamiento intermedio. El programa leer lneas de texto desde
el teclado y las escribe en un archivo de disco. Despus de escribir las
lneas, el programa leer de nuevo.
/* lectura y escritura sobre fichero a bajo nivel */
#include <stdio.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int input(char *buf, int fd1) ; /* prototipos de funcin */
int display(char *buf, int fd2) ;
#define BUF_SIZE 128 /* macro */
main()
{
char buf[BUF_SIZE] ;
int fd1, fd2 ;
fd1 = creat("dpe1", O_WRONLY) ;
if(fd1 == -1) /* apertura incorrecta? */
{
printf("no puede abrirse el archivo\n") ;
exit(1) ;
}
input(buf, fd1) ; /* lectura del fichero */
close(fd1) ; /* cierre del fichero */
fd2 = creat("dpe1", O_RDONLY) ;
Cap.7-25
Captulo 7: Ficheros ProgramacinenC
if(fd2 == -1) /* apertura incorrecta? */
{
printf("no puede abrirse el archivo\n") ;
exit(1) ;
}
display(buf, fd2) ; /* presenta el fichero */
close(fd2) ; /* cierre del fichero */
}
input(char *buf, int fd1) /* lee desde el teclado */
{
register int t ;
printf("introduce texto (quit en nueva lnea sale)\n") ;
do
{
for(t = 0 ; t < BUF_SIZE ; t++)
buf[t] = \0 ;
printf(": ") ;
gets(buf) ; /* introduce caracteres del teclado */
if(write(fd1, buf, BUF_SIZE) != BUF_SIZE)
{
printf("error de escritura\n") ;
exit(1) ;
}
}
while(strcmp(buf, "quit")) ;
}
display(char *buf, int fd2) /* presenta texto */
{
for(;;)
{
if(read(fd2, buf, BUF_SIZE) == 0)
return ;
printf("%s\n", buf) ;
}
}
ACCESO DIRECTO (ALEATORIO)
La llamada a la funcin lseek() proporciona el acceso directo a ficheros
de disco bajo el sistema de E/S sin memoria intermedia. El prototipo de su
declaracin es:
long lseek(int fd, long nmero_bytes, int origen) ;
Donde fp es un descriptor de archivo que creat() u open() devuelven.
nmero_bytes debe ser un long int y origen debe ser una de las macros
siguientes:
Origen Nombre Valor real
Comienzo archivo SEEK_SET 0
Posicin actual SEEK_CUR 1
Fin de archivo SEEK_END 2
Cap.7-26
Captulo 7: Ficheros ProgramacinenC
Por tanto, para saltar nmero_bytes desde el comienzo del archivo, se
debera poner SEEK_SET en origen. Para saltar desde la posicin actual, usar
SEEK_CUR; para saltar desde el final de archivo, usar SEEK_END.
La funcin lseek() devuelve nmero_bytes si todo va bien. Por tanto,
lseek() devolver un nmero entero long. Con fallo, lseek() devuelve -1L.
Un ejemplo sencillo que usa lseek() es otra versin del programa DUMP
desarrollado anteriormente. Esta versin se recodifica para usar el
tratamiento a bajo nivel del fichero. La versin no slo muestra el
funcionamiento de lseek(), sino tambin ilustra muchas de las funciones de E/S
sin memoria intermedia.
/* versin 2 del programa DUMP */
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <ctype.h>
#include <fcntl.h>
#define SIZE 128
char buf[SIZE] ;/* memoria habilitada por usuario para mantener */
/* los datos, equivale a memoria-intermedia */
void display(int num) ;
main(int argc, char *argv[])
{
char s[10] ;
int fd, sector, numread ;
long pos;
if(argc != 2)
{
printf("introducir el nombre el archivo\n") ;
exit(1) ;
}
fd = open(argv[1], O_RDONLY, 0) ;
if(fd == -1)
{
printf("no puede abrirse el archivo\n") ;
exit(1) ;
}
do
{
printf("\n\nbuffer: ") ;
gets(s) ;
sector = atoi(s);/* coge el sector para leer */
pos = (long) (sector * SIZE) ;
if(lseek(fd, pos, SEEK_SET) != pos)
printf("error de acceso\n") ;
numread = read(fd, buf, SIZE) ;
display(numread) ;
}
Cap.7-27
Captulo 7: Ficheros ProgramacinenC
while(numread > 0) ;
close(fd) ;
}
void display(int numread)
{
int i, j ;
for(i = 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") ;
}
}
La sentencia indicada anteriormente como:
lseek(fd, pos, SEEK_SET)...
Podra haberse definido de la siguiente forma:
lseek(5, 150L, 0)
Siendo
5 .- En el fichero 5
150 .- 150 caracteres (desplazamiento tipo long)
0 .- Por referencia al principio del fichero
BORRADO DE FICHEROS
Para eliminar un archivo del directorio, se utiliza la funcin unlink().
Aunque unlink() se considera como parte del sistema de E/S similar a UNIX,
quitar cualquier archivo desde el directorio. El prototipo de la llamada es:
int unlink(char *nombre_de_archivo) ;
Donde nombre_de_archivo es un carcter apuntado por cualquier nombre de
archivo vlido. La funcin unlink() devolver un error (usualmente -1) si no
pudo borrar el archivo. Esto pudo suceder si no estaba en el disquete o si el
disquete estaba protegido.
7.7 QU MTODO ELEGIR?
Para nuevos proyectos, es recomendable usar el sistema de E/S a nivel
de registro que define el estndar ANSI. Como el comit estndar ANSI ha
elegido no estandarizar el sistema de E/S a bajo nivel, no se puede considerar
Cap.7-28
Captulo 7: Ficheros ProgramacinenC
para proyectos futuros. Sin embargo, el cdigo existente permanecer durante
una serie de aos y probablemente y hay razn para reescribirlo en este
momento.
En el sistema de E/S con almacenamiento intermedio, se usar a nivel de
texto, getc() y putc(), cuando se trabaje con archivos de caracteres, tales
como los archivos que crea un procesador de texto. Sin embargo, cuando se
necesite guardar datos binarios o tipos de datos complejos, en archivos
binarios, debe utilizarse fread() y fwrite().
Una ltima palabra de advertencia: nunca tratar de mezclar los dos
sistemas de E/S en el mismo programa. La manera en que trata cada mtodo los
archivos es diferente y podra interferir accidentalmente.
Cap.7-29
Captulo 7: Ficheros ProgramacinenC
EJERCICIOS PROPUESTOS, CAPTULO 7
1) Realizar un programa que tome dos argumentos en la lnea de
rdenes. El primero es el nombre de un archivo, el segundo es un carcter. El
programa debe explorar el archivo especificado, buscando el carcter. Si el
archivo contiene al menos uno de estos caracteres, informa del hecho.
2) Escribir un programa que lea un archivo de texto y cuente cuntas
veces se repite cada letra de la A a la Z. Debe mostrar los resultados (no
diferenciar entre letras maysculas y minsculas).
3) Crear un programa que copie los contenidos de un archivo de texto
en otro. El programa debe aceptar tres argumentos en la lneas de rdenes. La
primera que sea el nombre del archivo fuente, la segunda el nombre del archivo
destino, y la tercera opcional. Si existe y es igual a "mirar" el programa
mostrar cada carcter al copiar los archivos; en caso contrario, no se
muestra ninguna salida por pantalla. Si no existe el archivo destino crearlo.
4) Obtener un programa que compare dos archivos, cuyos nombres se
especifican en la lnea de rdenes, y que presente en pantalla el mensaje "Los
archivos son iguales" si se cumple la condicin de igualdad, o bien muestre
el byte con la primera diferencia.
5) Realizar un programa que cuente el nmero de bytes de un archivo
(de texto o binario) y muestre el resultado. El usuario debe especificar el
archivo en la lnea de rdenes.
6) Escribir un programa que intercambie los contenidos de los dos
archivos cuyos nombres se especifican en la lnea de rdenes. Es decir, dados
dos archivos llamados FICH1 y FICH2, despus de ejecutarlo FICH1 contendr los
que inicialmente tena FICH2, y FICH2 almacenar el contenido de FICH1 (ayuda:
utilizar un archivo temporal para ayudar al proceso de intercambio).
7) Crear un programa que permita guardar un listn telefnico en un
archivo de disco. El programa debe mostrar el siguiente men:
1. Introducir nombre y nmeros
2. Buscar nmeros
3. Guardar listn en disco
4. Cargar listn desde el disco
5. Salir
El programa debe ser capaz de guardar 100 nombre y nmeros.
8) Realizar un programa que utilice fgets() para mostrar los
contenidos de un archivo de texto, pantalla a pantalla. Despus de que se
muestre la pantalla, el programa debe pedir ms al usuario.
9) Utilizando las funciones ftell() y fseek(), escribir un programa
que permita copiar los contenidos de un archivo en otro en orden inverso
(prestar especial atencin al modo en que se encuentra el final de fichero).
(Ayuda: algunos ficheros terminan en un indicador de fin_de_archivo, que no
se debe copiar).
10) Escribir un programa que muestre los contenidos de un archivo de
texto (especificado en la lnea de rdenes) lnea a lnea. Despus de que se
muestre cada lnea, debe preguntar al usuario si quiere ver otra lnea.
11) Crear un programa que utilice fwrite() para escribir 100 enteros
generados aleatoriamente en un archivo llamado RAND.
12) Escribir un programa que utilice fread() para mostrar los enteros
almacenados en el archivo RAND creado en el ejercicio anterior.
Cap.7-30
ANEXO 1
Conceptos Bsicos
Anexo-1 Programacin en C
INTRODUCCIN
Un PROGRAMA es un conjunto de mandatos que puede someterse, como una
unidad, a un ordenador y utilizarse para dirigir su comportamiento. Cada uno
de los mandatos recibe el nombre de SENTENCIA o INSTRUCCIN.
Todo programa est constituido por uno o varios algoritmos
1
La construccin de un algoritmo debe ser lo ms independiente posible
de los lenguajes de programacin. Para llegar a la solucin (algoritmo) que
resuelve un problema, se debe pasar por varios niveles de abstraccin. El
nivel mximo ha de ser totalmente independiente de los lenguajes de
programacin; en cambio, el nivel menos abstracto ha de estar orientado al
lenguaje de programacin que se utilice.
CICLO DE VIDA DE UN PROGRAMA O APLICACIN
Disear un programa no se reduce slo a escribir un determinado
algoritmo en algn lenguaje de programacin; esto slo se corresponde dentro
del ciclo de vida de un programa con la fase de codificacin.
La vida de un programa abarca desde el momento que se establece la
definicin del problema hasta que dicha aplicacin es desechada.
Representacin grfica del ciclo de vida de una
aplicacin
1 Algoritmo: Es una especificacin paso a paso de la resolucin de un problema dado.
Anexo1-2
Anexo-1 Programacin en C
DEFINICIN Y ENUNCIADO
En esta fase debe definirse con claridad todo lo que atae al problema
que se quiere resolver. Esta definicin incluir una lista completa de todos
los requisitos que se deben cumplir y se especificar la solucin esperada
tanto para los datos de entrada correctos como para los incorrectos. El
programa ha de responder en todos los casos sin que se origine una
finalizacin anormal del mismo.
DISEO O ANLISIS
Puede dividirse en alto y bajo nivel. El primero consiste en realizar
el planteamiento de la solucin. Un problema no se suele componer de una sola
tarea, sino de varias relacionadas. Por ello, una solucin debe especificar
las tareas que se deben llevar a cabo, las responsabilidades de cada una y la
forma de relacionarse e intercalarse las distintas tareas.
El diseo a bajo nivel consistir en la seleccin de los algoritmos ms
apropiados y su representacin en algn pseudocdigo, as como en la seleccin
de las estructuras de datos ms adecuadas.
CODIFICACIN
Se debe elegir el lenguaje de programacin ms adecuado al tipo de
problema o a los algoritmos y estructuras de datos seleccionados en la etapa
de diseo. Independientemente del lenguaje elegido es conveniente utilizar un
estilo de programacin que sea fcilmente legible y pueda ser seguido por
cualquier persona. El estilo ms extendido se basa en la utilizacin de los
sangrados de lneas (endentacin). La codificacin de los algoritmos debe
presentar el mayor grado de eficiencia en cuanto a: Ejecucin, cdigo, memoria
y Entrada/Salida.
COMPROBACIN Y DEPURACIN
No basta con escribir y ejecutar un programa correctamente segn las
especificaciones realizadas por los usuarios. Es necesario comprobar que los
resultados son los adecuados en todos los casos, incluso en aquellos que no
hayan sido comprobados explcitamente. Un modo de garantizar el buen
funcionamiento del programa es la validacin con respecto a sus
especificaciones.
La depuracin de un programa consiste en detectar los casos ms
desfavorables que puede encontrarse la aplicacin por ejemplo situaciones que
abortaran la ejecucin o bloquearan el ordenador (pulsacin de un tecla
indebida). La depuracin tambin contempla la eliminacin del cdigo fuente
redundante.
DOCUMENTACIN Y MANTENIMIENTO
La documentacin de un programa es un proceso continuo a lo largo de
todo el ciclo de vida. Cada etapa produce una documentacin especfica.
Despus de haber comprobado que el programa funciona correctamente, habr que
recopilar toda esta informacin producida a lo largo del proceso, completarla
y organizarla para elaborar una documentacin del programa que sea til para
sus usuarios y para los programadores encargados de su mantenimiento. Los
programadores deben documentar el cdigo producido por ellos. Esta ltima
documentacin consiste en colocar los comentarios que sean precisos entre las
lneas de cdigo.
Los programas no son entes estticos, sino que deben ser revisados y
actualizados constantemente porque se descubren nuevos errores, se modifican
Anexo1-3
Anexo-1 Programacin en C
alguna especificacin, se cambia de ordenador, etc. La fase en la cual se
efectan estas modificaciones posteriores se denomina mantenimiento. Una buena
documentacin facilita el mantenimiento de los programas de forma clara,
sistemtica y legible, la etapa de mantenimiento puede ser muy difcil,
incluso imposible.
Si se descubren errores en las fases anteriores, se tendr que empezar
de nuevo desde la etapa correspondiente. La fase de mantenimiento implicar,
en muchos casos, repetir varias de las etapas anteriores, segn se trate de
cambios en la especificacin, sustitucin de un algoritmo por otro considerado
mejor, etc.
LENGUAJES DE PROGRAMACIN
A la hora de describir al ordenador como resolver un problema, es
necesario formalizarlo adecuadamente para que pueda entenderlo. Esta
formalizacin se realiza a travs de un lenguaje de programacin.
Se entiende por LENGUAJE DE PROGRAMACIN la notacin para la descripcin
precisa de algoritmos o programas informticos. Los lenguajes de programacin
son artificiales en los que se definen de forma estricta la sintaxis y la
semntica.
Existen cinco generaciones de lenguajes de programacin:
PRIMERA GENERACIN
Est formada por los siguientes lenguajes: binario, mquina, ensamblador
y macroensamblador. El alfabeto del lenguaje binario es el 0 y el 1, es decir,
cada instruccin se corresponde con una determinada configuracin de ceros y
unos. A partir del lenguaje mquina, las instrucciones (mandatos) se
representan mediante identificadores que estn compuestos de caracteres
alfanumricos; estos lenguajes necesitan de un traductor para transformar los
programas al lenguaje binario. A estos lenguajes tambin se les llama
lenguajes de bajo nivel.
SEGUNDA GENERACIN
La componen los siguientes lenguajes: FORTRAN, COBOL, ALGOL y BASIC.
Tambin llamados lenguajes de alto nivel y de programacin clsica. Los
lenguajes de esta generacin se pueden dividir en dos amplias categoras:
cientficos y de gestin.
TERCERA GENERACIN
Estos lenguajes (tambin denominados lenguajes de programacin moderna
o estructurada) estn caracterizados por sus potentes posibilidades
procedimentales y de estructuracin de datos. Se pueden dividir en dos grupos:
lenguajes de alto orden de propsito general y lenguajes especializados.
El primer grupo est constituido por los lenguajes PL/1, PASCAL, MODULA-
2, C y ADA. Todos ellos descendientes del ALGOL. Estos lenguajes se utilizan
en la construccin de gran espectro de aplicaciones (ciencia e ingeniera,
comerciales, programacin de sistemas y otros).
Los lenguajes especializados estn caracterizados por su usual
formulacin sintctica, que ha sido especialmente diseada para una aplicacin
particular. Se suelen utilizar en el mundo de la ingeniera de software.
Ejemplos de ellos pueden ser LISP, PROLOG, APL, FORTH, etc.
Anexo1-4
Anexo-1 Programacin en C
CUARTA GENERACIN
El nivel de abstraccin va creciendo desde la primera a la tercera
generacin. Los lenguajes de cuarta generacin incrementan, an ms el nivel
de abstraccin. El control y las estructuras de datos poseen una sintaxis
distinta a la de anteriores generaciones con el fin de elevar el nivel de
abstraccin.
Se pueden clasificar en dos grandes categoras: lenguajes de peticin
y generacin de programas. Los primeros se han desarrollado para ser
utilizados conjuntamente con aplicaciones de bases de datos. Los segundos,
permiten al usuario crear programas en un lenguaje de tercera generacin
usando notablemente menos sentencias.
QUINTA GENERACIN
Varios procesadores modernos no soportan el ensamblador. Es decir son
programados directamente en un lenguajes especializado de alto nivel como LISP
cdigo-P. Los lenguajes de quinta generacin soportan el diseo orientado al
objeto, el procesamiento paralelo y vectorial. Suelen ser de propsito
particular. Son empleados en aplicaciones de investigacin y de construccin
de prototipos. Con la aparicin del los sistemas operativos OS/2 y la serie
Windows se han desarrollado programas que permiten la programacin visual.
TRADUCTORES Y COMPILADORES
Un programa codificado en lenguaje de alto nivel se pasa al lenguaje de
la mquina a travs del traductor correspondiente a ese lenguaje. Por lo
tanto, un traductor es un programa que toma, como entrada, un programa escrito
en un lenguaje (lenguaje fuente) y produce, como salida, un programa escrito
en otro lenguaje (lenguaje objeto).
Si el lenguaje fuente es de alto nivel (como FORTRAN, BASIC o Pascal)
y el lenguaje objeto es un lenguaje de bajo nivel (como ensamblador o lenguaje
mquina), el traductor correspondiente se llama compilador. El compilador toma
como entrada las instrucciones del lenguaje fuente y genera como salida
instrucciones del lenguaje objeto; ste puede ser binario o mquina.
El intrprete, a medida que traduce cada instruccin, la ejecuta sobre
los datos que le corresponden (cero o ms de un dato). Cuando ha terminado de
ejecutar esa instruccin de alto nivel, repite el mismo proceso con la
siguiente instruccin. De esa manera va avanzando hasta que se haya ejecutado
todo el programa de alto nivel segn los datos de partida. El intrprete es,
en general, ms pequeo que un compilador; pero el tiempo que tarda en
traducir y ejecutar un programa fuente es ms grande que el empleado por el
compilador. Cuando se interpreta un bucle de instrucciones, stas se traducen
y ejecutan tantas veces como se repite el bucle.
Si el lenguaje fuente es ensamblador y el lenguaje objeto es cdigo de
mquina, entonces el traductor se llama ensamblador.
Si, por ejemplo, un compilador puede ejecutarse en un procesador (INTEL
80486) y producir cdigo para otro procesador (MOTOROLA 68040), entonces se
trata de un compilador cruzado.
OBTENCIN DE UN PROGRAMA EN LENGUAJE C
Para la creacin de un programa informtico que pueden ejecutarse en el
ordenador (.COM o .EXE), deben suceder varias etapas bien definidas dentro del
entorno integrado de Turbo C. Dichas etapas son:
Anexo1-5
Anexo-1 Programacin en C
A) Generacin del programa en cdigo fuente (.C).
B) Compilacin, obtencin del mdulo objeto (.OBJ).
C) Linkado (.EXE o .COM).
Una vez concluido este proceso, el fichero generado puede ser ejecutado
por el PC de forma autnoma generalmente.
El apartado A consiste en el diseo de un fichero de texto (obtenido a
partir de un editor "ASCII", es decir, que no incorpore caracteres de control
, por ejemplo WordStar, E, Edit -MsDos-, etc.) que contendr un nmero
determinado de sentencias (instrucciones propias del lenguaje C): estructuras
de control, rdenes de entrada y salida, etc. En un formato legible por el
programador y que deben cumplir las reglas marcadas por el compilador
(sintaxis, etc.).
Una vez finalizado el fichero con extensin .C (regla no obligatoria
pero si establecida convencionalmente por los programadores para sealar un
fichero fuente escrito en lenguaje C) hay que proceder a su compilacin
- Apartado B - esto es, convertir el fichero para que sea comprensible al
ordenador (mdulo objeto).
Generalmente el compilador realiza dos "pasadas" sobre el fichero de
texto. En la primera buscar aquellas instrucciones dirigidas al propio
compilador y que no pertenecen al lenguaje de programacin en s. La misin
de estas pseudoinstrucciones (denominadas directivas o instrucciones del
preprocesador) es la de realizar un tratamiento determinado al contenido del
fichero, por ejemplo la directiva:
# include <------.h>
Manda al preprocesador que la informacin del archivo que la acompaa
(.h inicial de "header" representa un fichero cabecera que contiene
informacin sobre las funciones contenidas en la biblioteca de C) sea incluida
en el propio fichero fuente.
Durante la fase de reubicado o linkado - Apartado C- el enlazador
("linker") recorre el mdulo objeto y encuentra que falta las funciones de la
biblioteca (en el subdirectorio LIB) que se han declarado al compilador con
la directiva correspondiente, y las aade, ya compiladas, adecuadamente en el
lugar donde han sido llamadas.
El enlazador es una herramienta que combina el programa de usuario con
las funciones requeridas de la biblioteca. Dichas funciones son de formato
reubicable. Esto significa que las funciones no definen las direcciones
absolutas de la memoria para las diversas instrucciones en cdigo mquina,
sino que mantienen la informacin de los desplazamientos relativos. Cuando se
enlaza el programa con las funciones de la biblioteca estndar de turbo C usa
los desplazamientos de la memoria para crear las direcciones reales usadas.
Anexo1-6
ANEXO 2
Entorno Turbo C
Anexo-2 Programacin en C
INTRODUCCIN
Turbo C tiene dos modos separados de operacin. El primero es su entorno
integrado de desarrollo. En este entorno se puede controlar la edicin,
compilacin y ejecucin usando pocas pulsaciones y mens fcil de utilizar.
El otro modo de operacin utiliza el mtodo tradicional, segn el cual se usa:
1) Un editor (ASCII) para crear el archivo fuente.
2) El compilador que genere el mdulo objeto (legible por la mquina).
3) El "linkador" o reubicador para ensamblar varios mdulos .OBJ en uno
ejecutable.
Este modo se llama mtodo de lnea de rdenes.
El propsito de este anexo es mostrar el entorno integrado de Turbo C.
Se ha utilizado la versin 2.0 de Turbo C. Otras versiones se diferencian de
sta ligeramente.
EL PROGRAMA TCINST
Turbo C incluye un programa de instalacin llamado TCINST, que se usa
para establecer diversos atributos y especificaciones por defecto del entorno
integrado de Turbo C. Para ejectar este programa, simplemente teclear TCINST
en el indicativo del sistema. Cuando comienza la ejecucin del programa, se
ver un men que contiene los siguientes elementos:
Installation Menu
Compile
Project
Options
Debug
Editor commands
Mode for display
Set colors
Resize windows
Quit/save
Turbo C Installation Program 2.0
A continuacin se examinan cada una de estas opciones.
COMPILE
Seleccionar la opcin Compile, para definir un archivo primario por
defecto.
PROJECT
Con la opcin Project se puede definir el nombre de proyecto. Tambin
permite establecer el estado de las caractersticas relacionadas con el
proyecto por defecto.
OPTIONS
La eleccin de Options permite establecer los diferentes parmetros de
entorno, enlazador y compilador.
Anexo2-2
Anexo-2 Programacin en C
DEBUG
La opcin Debug permite determinar como funciona el depurador
incorporado.
EDITOR COMMANDS
Seleccionando la opcin Editor commands se puede adaptar el editor del
Turbo C. Si se est familiarizado con otro tipo de editor, se puede hacer que
el editor de Turbo C lo imite cambiando las teclas que activan cada orden.
Install Editor
Command name Primary Secondary
New Line * <CtrlM> <CtrlM>
Cursor Left * <CtrlS> <Lft>
Cursor Right * <CtrlD> <Rgt>
Word Left * <CtrlA> <CtrlLft>
Word Right * <CtrlF> <CtrlRgt>
Cursor Up * <CtrlE> <Up>
Cursor Down * <CtrlX> <Dn>
Scroll Up * <CtrlW>
Scroll Down * <CtrlZ>
Page Up * <CtrlR> <PgUp>
Page Down * <CtrlC> <PgDn>
Left of Line * <CtrlQ><CtrlS> <Home>
Right of Line * <CtrlQ><CtrlD> <End>
Top of Screen * <CtrlQ><CtrlE> <CtrlHome>
Bottom of Screen * <CtrlQ><CtrlX> <CtrlEnd>
Top of File * <CtrlQ><CtrlR> <CtrlPgUp>
Bottom of File * <CtrlQ><CtrlC> <CtrlPgDn>
Move to Block Begin * <CtrlQ><CtrlB>
Move to Block End * <CtrlQ><CtrlK>
Move to Previous Pos * <CtrlQ><CtrlP>
Move to Marker 0 * <CtrlQ>0
Move to Marker 1 * <CtrlQ>1
Move to Marker 2 * <CtrlQ>2
Move to Marker 3 * <CtrlQ>3
Toggle Insert * <CtrlV> <Ins>
Insert Line * <CtrlN>
Delete Line * <CtrlY>
Delete to End of Line * <CtrlQ><CtrlY>
Delete Word * <CtrlT>
Delete Char * <CtrlG> <Del>
Delete Char Left * <CtrlBkSp> <CtrlH>
Set Block Begin * <CtrlK><CtrlB>
Set Block End * <CtrlK><CtrlK>
Mark Word * <CtrlK><CtrlT>
Hide Block * <CtrlK><CtrlH>
Set Marker 0 * <CtrlK>0
Set Marker 1 * <CtrlK>1
Set Marker 2 * <CtrlK>2
Set Marker 3 * <CtrlK>3
Copy Block * <CtrlK><CtrlC>
Move Block * <CtrlK><CtrlV>
Delete Block * <CtrlK><CtrlY>
Read Block * <CtrlK><CtrlR>
Write Block * <CtrlK><CtrlW>
Print Block * <CtrlK><CtrlP>
Exit Editor * <CtrlK><CtrlD> * <CtrlK><CtrlQ>
Tab * <CtrlI>
Toggle Autoindent * <CtrlO><CtrlI> * <CtrlQ><CtrlI>
Toggle Tabs * <CtrlO><CtrlT> * <CtrlQ><CtrlT>
Restore Line * <CtrlQ><CtrlL>
Find String * <CtrlQ><CtrlF>
Find and Replace * <CtrlQ><CtrlA>
Search Again * <CtrlL>
Insert Control Char * <CtrlP>
Save file * <CtrlK><CtrlS>
Match pair * <CtrlQ><Ctrl[>
Match pair backward * <CtrlQ><Ctrl]>
Language Help * <CtrlF1>
Toggle optimal fill * <CtrlO><CtrlF>
Toggle unindent * <CtrlO><CtrlU>
Block indent * <CtrlK><CtrlI>
Block unindent * <CtrlK><CtrlU>
<--> select PgUp-PgDn-page < -modify R-restore factory defaults ESC-exit
F4-Key modes: (*)-WordStar-like ( )-Ignore case ()-Verbatim
La columna de teclas activadoras de la izquierda contiene las teclas
primarias para hacer funcionar una orden. Por defecto estas teclas son como
las de Wordstar. La columna de la derecha contiene las teclas de activacin
Anexo2-3
Anexo-2 Programacin en C
que invocan la misma orden.
Algo que debe tenerse en cuenta es que no se debera dar a las rdenes
del editor las mismas secuencias que las que activan el entorno integrado.
MODE FOR DISPLAY
Se utiliza para determinar cmo va a comunicarse Turbo C con el
controlador del vdeo en la computadora.
SET COLORS
Selecciona el esquema de colores que Turbo C usa cuando funciona en un
entorno en color. Turbo C tiene tres opciones de color incorporadas. Adems
se puede definir el propio esquema para cada parte de la interfaz de Turbo C
con el usuario.
RESIZE WINDOWS
Se puede utilizar esta opcin para cambiar el tamao de las ventanas de
edicin y mensaje una respecto a la otra. Despus de seleccionar esta opcin,
se pueden usar las teclas de flecha arriba y abajo para mover la lnea que
separa las dos ventanas.
QUIT/SAVE
Termina la instalacin del programa. Turbo C preguntar si se quieren
guardar los cambios hechos con este programa; si es as, se convertirn en el
modo por defecto de Options.
ENTORNO TURBO C
Para ejecutar la versin integrada de Turbo C, siempre teclear TC y
pulsar <Enter> (< ). Cuando Turbo C comienza se ver la pantalla que se
muestra a continuacin:
File Edit Run Compile Project Options Debug Break/watch
Edit
Line 1 Col 1 Insert Indent Tab Fill Unindent C:NONAME.C
File Run Compile
Load F3 Run Ctrl-F9 Compile to OBJ C:NONAME.OBJ
Pick Alt-F3 Program reset Ctrl-F2 Make EXE file C:NONAME.EXE
New Go to cursor F4 Link EXE file
Save F2 Trace into F7 Build all
Write to Step over F8 Primary C file:
Directory User screen Alt-F5 Get info
Change dir
OS shell Break/watch
Quit Alt-X Add watch Ctrl-F7 Toggle breakpoint Ctrl-F8
Delete watch Clear all breakpoints
Edit watch View next breakpoint
Options Remove all watches
Compiler Debug
Linker Project Evaluate Ctrl-F4
Environment Project name Call stack Ctrl-F3
Directories Break make on Errors Find function
Arguments Auto dependencies Off Refresh display
Save options Clear project Display swapping Smart
Retrieve options Remove messages Source debugging On
Message
F1-Help F5-Zoom F6-Switch F7-Trace F8-Step F9-Make F10-Menu
Anexo2-4
Anexo-2 Programacin en C
A sta se le llama la PANTALLA PRINCIPAL DE MEN y consta de cuatro
partes, listadas aqu en orden de arriba a abajo:
.- El men principal.
.- La lnea y ventana del editor.
.- La ventana de mensaje del compilador.
.- La lnea de referencia rpida de las teclas "calientes".
EL MEN PRINCIPAL
Se usa para decirle al Turbo C que haga algo, como ejecutar el editor
o compilar un programa, o establecer una opcin de entorno. En el men
principal se selecciona de dos maneras:
1) Se pueden utilizar las teclas del cursor para mover la barra
iluminada a la opcin que se quiera y entonces pulsar <Enter>.
2) Pulsar la primera letra (mayscula o minscula) de la opcin del
men que se quiere abrir.
COMANDOS DEL MEN PRINCIPAL
A continuacin se analizan los comandos y subcomandos dependientes del
men principal contenidos en ventanas del tipo "pull-down" (men desplegable).
File
Load F3
Pick Alt-F3
New
Save F2
Write to
Directory
Change dir
OS shell
Quit Alt-X
FILE.- Carga y guarda archivos, directorios, invoca DOS y sale de Turbo
C.
SUBCOMANDOS
LOAD.- Espera el nombre de un archivo y entonces lo carga en el editor.
PICK.- Visualiza un men que lista los ltimos ocho archivos que se
cargaron en el entorno integrado. Se puede seleccionar uno de ellos
usando las teclas de flechas para mover la barra iluminada sobre l y
pulsando <Enter>.
NEW.- Permite editar un nuevo archivo.
SAVE.- Guarda el archivo que est en ese momento en el editor.
WRITE TO.- Permite guardar un archivo bajo otro nombre diferente del
anterior.
DIRECTORY.- Visualiza el directorio actual de trabajo y permite elegir
un archivo para editar.
CHANGE DIR.- Cambia el directorio por defecto a otro que se
especifique.
Anexo2-5
Anexo-2 Programacin en C
OS SHELL.- Va al indicativo del sistema y permite ejecutar rdenes de
DOS. Bajo esta opcin se debe teclear EXIT para volver al entorno.
EDIT.- Invoca al editor de turbo C. (ver anexo 3).
Run
Run Ctrl-F9
Program reset Ctrl-F2
Go to cursor F4
Trace into F7
Step over F8
User screen Alt-F5
RUN.- Compila, enlaza y ejecuta el programa cargado en el entorno.
SUBCOMANDOS
RUN.- Ejecuta el programa actual. Si todava no se ha compilado el
programa. Run lo compila. El resto de las opciones estn relacionadas
con la ejecucin de un programa utilizando el depurador.
PROGRAM RESET.- Termina el programa cuando se est ejecutando en modo
depuracin (debug).
GO TO CURSOR.- Ejecuta el programa hasta que alcanza la lnea de
cdigo donde est situado el cursor.
TRACE INTO.- Ejecuta la siguiente sentencia. Si esa sentencia incluye
una llamada a subrutina, la ejecucin se detiene al principio de la
subrutina.
STEP OVER.- Ejecuta la siguiente lnea de cdigo, pero no rastrea en
ninguna subrutina que pueda haberse llamado.
USER SCREEN.- Pantalla del indicativo del sistema donde se muestran
los resultados del programa. Se retorna tras pulsar una tecla.
Compile
Compile to OBJ C:NONAME.OBJ
Make EXE file C:NONAME.EXE
Link EXE file
Build all
Primary C file:
Get info
COMPILE.- Compila el programa del entorno.
SUBCOMANDOS
COMPILE to OBJ.- Permite compilar el archivo que est actualmente en
el editor como cdigo fuente(.C) en un archivo .OBJ (archivo objeto
relocalizable que posteriormente se debe enlazar par obtener un archivo
.EXE que puede ejecutarse).
MAKE EXE FILE.- Compila directamente el programa en un archivo
ejecutable.
Anexo2-6
Anexo-2 Programacin en C
LINK EXE FILE.- Realiza la fase de "linkado" del fichero presente en
pantalla. sin no existe el mdulo .OBJ lo crea.
BUILD ALL.- Recompila todos los archivos relacionados con el programa.
PRIMARY C FILE.- Permite especificar el nombre de archivo que se va
a compilar cuando se haya seleccionado una de los opciones primeras,
ignorando qu archivo se est editando actualmente.
GET INFO.- Visualiza la informacin del programa y del entorno,
incluyendo la cantidad de memoria que se dispone y la longitud del
archivo.
Project
Project name
Break make on Errors
Auto dependencies Off
Clear project
Remove messages
PROJECT.- Permite especificar un archivo de proyecto (programa de
archivos mltiples). Cada proyecto se asocia con un project file, que
determina qu archivos son parte del proyecto. Todos los archivos de proyecto
deben finalizar con una extensin .PRJ.
SUBCOMANDOS
PROYECT NAME.- Turbo C solicita el nombre del proyecto.
BREAK MAKE ON.- Indica la situacin en la que se detiene la ejecucin
de la compilacin y "linkado" (reubicacin) del proyecto. puede ser por:
. Warnings
. Errors
. Fatal Errors
. Link
AUTO DEPENDENCIES.- (off/on) verifica posibles modificaciones de los
ficheros .OBJ que forman parte del proyecto, antes de compilar y
recolocar los mismos.
CLEAR PROJECT.- Elimina el nombre del proyecto y limpia la ventaja
de mensajes.
REMOVE MESSAGES.- Quita los mensajes de error desde la ventana de
mensajes.
Options
Compiler
Linker
Environment
Directories
Arguments
Save options
Retrieve options
OPTIONS.- Establece varias opciones del compilador, enlazador y entorno.
Anexo2-7
Anexo-2 Programacin en C
SUBCOMANDOS
COMPILER.- Despus de seleccionar Compiler, se visualizan estas
opciones:
Model.- Selecciona el modelo de memoria (debido a la arquitectura
de la familia 8086) que se usa para compilar el programa. Por
defecto es small y se adapta a la mayora de las aplicaciones.
Defines.- Define temporalmente smbolos del preprocesador que
el programa usar automticamente. Esta caracterstica es muy til
cuando se depura el programa.
Code generation.- Presenta una serie de opciones que se pueden
establecer:
CALLING CONVENTION.- Se puede escoger entre la convencin
de llamadas de C y la de Pascal. Una convencin de llamadas
es simplemente el mtodo en que un lenguaje de programacin
llama a las funciones y pasa argumentos. Generalmente, se
usara la convencin de llamadas C.
INSTRUCTION SET.- Si se sabe que el cdigo objeto del
programa se usar en un procesador 80186/80286, entonces se
puede seleccionar Instruction set. Esta opcin provocar que
el programa funcione ms de prisa, pero el programa no
funcionar en computadoras que se basan en el 8088/8086. Por
defecto estn las instrucciones del 8088/8086.
FLOATING POINT.- Se puede escoger la forma en que Turbo C
implementa operaciones en punto flotante. El mtodo por
defecto es usar las rutinas de emulacin 8087/80287. Cuando
uno de estos coprocesadores matemticos est en el sistema,
permite operaciones extremadamente rpidas en coma
flotante. Sin embargo si no se tiene un coprocesador
matemtico o si el programa se usara en varias computadoras,
se puede emular el funcionamiento del 8087 por software, un
proceso mucho ms complejo.
DEFAULT CHAR TYPE.- Determina por defecto si char va con
signo o sin l. Por defecto va con signo.
ALIGNMENT.- Determina si los datos se organizan en byte o
palabras. En los procesadores 8086/286, los accesos a
memoria son ms rpidos si los datos se organizan en
palabras. Sin embargo, no hay diferencias en el 8088. Por
defecto est organizado en palabras.
GENERATE UNDERBARS.- determina si Turbo C aadir un
subrayado al comienzo de cada indentificador en el archivo
enlazado. Para la mayora, no se desactiva esta opcin a
menos que se sea un programados experto y se entienda el
funcionamiento interno de Turbo C.
MERGE DUPLICATE STRINGS.- Una optimizacin comn del
compilador que realiza Turbo C es la eliminacin de las
constantes duplicadas de cadena. As Turbo C mezcla todas
las cadenas duplicadas en una sola. Se puede detener esta
mezcla cambiando la opcin. Queda fuera por defecto.
STANDARD STACK FRAME.- Se fuerza a Turbo C a generar
llamadas estndar y devolver el cdigo para cada llamada de
funcin. Hacer esto ayuda a depurar el proceso permitiendo
Anexo2-8
Anexo-2 Programacin en C
que se visualice la llamada a la pila.
TEST STACK OVERFLOW.- Turbo C comprueba el desbordamiento
de la pila activando esta opcin, provocando que el programa
funcione ms lentamente pero necesario para encontrar
algunos problemas.
OBJ DEBUG INFORMATION.- Controla si se ha compilado en el
archivo la informacin a corregir, porque cuando se est
utilizando el corrector de errores se necesita esta
informacin. Por defecto est siempre funcionando.
Optimizacion.- Bajo la opcin Optimization estn estas cuatro
posibilidades:
OPTIMIZE FOR SIZE/SPEED.- Algunas optimizaciones que hacen
el cdigo ms pequeo tambin lo hacen ms lento. Otras
optimizaciones hacen el cdigo ms rpido pero ms grande.
A travs de esta opcin, Turbo C permite decidir qu
consideraciones -velocidad o tamao- es ms importante . Por
defecto es tamao.
USE REGISTER VARIABLES ON/OFF.- Cuando est desactivada se
suspende la utilizacin de variables de registro. A menos
que se interacte con cdigo no Turbo C, se debe dejar esta
opcin activa.
REGISTER OPTIMIZATION OFF/ON.- (Desactivada por defecto),
realiza optimizaciones adicionales. Dejarla desactivada
hasta que se comprenda el funcionamiento interno de Turbo
C.
JUMP OPTIMIZATION OFF/ON.- Activada permite reorganizar el
cdigo en sentencias de bucles y de interrupcin. Esta
configuracin puede provocar un gran rendimiento. Sin
embargo, si se usa un depurador sobre el cdigo objeto,
entonces desactivar esta opcin.
Source.- Establece el nmero de caracteres significativos en un
identificador, determina si los comentarios pueden estar anidados
y fuerza a Turbo C a aceptar slo las palabras clave de ANSI.
Generalmente se debe esta opcin en sus valores por defecto.
En su forma estndar, C (incluyendo Turbo C) no permite un
comentario dentro de otro.
Errors.- Configura la forma en que Turbo C dar los errores
durante la compilacin. Se puede establecer la forma en que Turbo
C informa de los errores y los avisos hasta que pare el proceso.
Se puede, adems, seleccionar precisamente el tipo de avisos que
se visualizarn.
Names.- Permite cambiar los nombres que Turbo C usa para los
diversos segmentos de memoria que usa el programa. No cambiarlos
a menos que se sepa verdaderamente cmo hacerlo.
LINKER.- Al seleccionar las opciones Linker, se ver la siguiente
lista de posibilidades. (Hasta que se llega a ser un programador
avanzado de Turbo C, probablemente no se cambiar ninguna).
Map File.- Crea un archivo de mapa del programa compilado. Un
archivo de mapa muestra las posiciones relativas de las variables
y funciones que integran el programa y dnde residen en la
Anexo2-9
Anexo-2 Programacin en C
memoria. Sirve para depurar ciertos programas en situaciones
complejas. Se puede crear un archivo de mapa de tres formas:
- Slo los segmentos.
- Smbolos pblicos o globales.
- Mapa detallado y completo.
Initialize segments.- Fuerza al enlazador a inicializar los
segmentos (normalmente desactivada, se puede activar en
situaciones muy especiales).
Default libraries.- Se aplica slo cuando se estn enlazando
mdulos que compilaron otros compiladores C. Por defecto est
desactivada. Si se activa, el enlazador buscar estas bibliotecas
que se definieron en mdulos separados antes de buscar las de
Turbo C.
Graphics library.- Determina si el enlazador ("linker")
alcanza la biblioteca de grficos.
Warn duplicate symbols.- Por defecto no est activo, Esto
significa que al tener mltiples identificadores globales
definidos, el enlazador elige cul va a usar.
Stack warning.- Si se usa Turbo C para crear rutinas que
enlazarn con programas externos en ensamblador, se podra recibir
el mensaje en tiempo de enlace "No stack specified". Para
eliminarlo desactivar Stack warning.
Case-sensitive link.- Est activa por defecto porque C es
sensible a "Case". Sin embargo, si se intenta enlazar mdulos
Turbo C con mdulos FORTRAN, por ejemplo, se puede necesitar la
desactivacin de esta opcin.
ENVIRONMENT.- Permite cambiar la forma de trabajar del entorno
integrado de Turbo C. Estas son las opciones:
Message tracking current file/off/all files.- Por defecto
se visualizan errores que solamente se encuentran en el archivo
fuente actual. Sin embargo, se puede dar orden de visualizar
mensajes en todos los archivos relacionados con un programa o de
no visualizar los errores.
Keep messages yes/no.- Si la opcin no aparece (no est por
defecto), antes de cada recompilacin, se borran todos los
mensajes de error. En caso de que s que est, se retienen los
mensajes antiguos y los nuevos se aaden a la lista.
Config auto save on/off.- Por defecto se encuentra
desactivado. Esto quiere decir que los cambios en la configuracin
de Turbo C se salvan nicamente si se emite una orden explcita.
Si esta opcin est activada, los cambios se salvan
automticamente.
Edit auto save on/off.- Por defecto no aparece. Si se
encuentra activada, el archivo fuente, que se est editando, se
salva automticamente antes de cada ejecucin, si ha habido
modificaciones.
Backup files on/off.- Cuando se salva un archivo, por defecto
se renombre automticamente la versin anterior: de extensin .C
pasa a .BAK. De esta manera, siempre se tiene la primera versin
Anexo2-10
Anexo-2 Programacin en C
como una copia de seguridad. Puede quitarse esta opcin
conmutndola. La nica razn para quitar esta opcin sera por el
espacio limitado del disco.
Tab size.- Establece el tamao del tabulador.
Zoomed windows on/off.- Utiliza las ventanas en zoom por
defecto.
Screen lines.- se puede utiliza un modo de vdeo que por
pantalla permita de 43 lneas (EGA) a 50 (VGA).
DIRECTORIES.- Despus de elegir la opcin Directories se pueden
especificar los caminos de bsqueda con las opciones siguiente:
. Include directories
. Library directories
. Output directories
. Turbo C directory
. Pick file name
Con las tres primeras opciones se pueden especificar los caminos de
bsqueda de los elementos indicados. Puede especificarse ms de un
camino utilizando una lista separada por puntos y comas. Y tambin el
camino al archivo de configuracin de Turbo C directory. La ltima
opcin especifica el camino de un archivo "pick".
ARG.- Cuando se ejecuta un programa en un entorno integrado, no se
teclea el nombre del programa como se hara en el indicador del DOS. Por
tanto, no se pueden especificar argumentos directamente cuando se
ejecuta un programa en el entorno interactivo. Sin embargo, Turbo C
trata este problema permitiendo especificar argumentos en la lnea de
rdenes a travs de la opcin Args.
Cuando se selecciona esta opcin, Turbo C pide que se introduzcan los
parmetros de la lnea de rdenes que precise el programa. Introducir
los parmetros deseados (pero no el nombre del programa). Entonces cada
vez que se ejecuta el programa. Turbo C usar los parmetros de la lnea
de rdenes especificados.
SAVE / RETRIEVE OPTIONS.- Despus de haber personalizado Turbo C
cambiando diversas opciones, se tienen dos posibilidades: usarlas slo
durante la presente sesin o guardarlas. Las entradas del men Retrieve
options y Save options permiten salvar y recuperar las opciones. Por
defecto el nombre del archivo es TCCONFIG.TC que se carga
automticamente.
Debug
Evaluate Ctrl-F4
Call stack Ctrl-F3
Find function
Refresh display
Display swapping Smart
Source debugging On
DEBUG.- Un depurador tradicional est diseado para proporcionar
depuracin al cdigo objeto, en donde se pueden vigilar los contenidos de los
registros de la CPU o los contenidos de la memoria. El depurador a nivel
fuente (que es el contenido en el entorno) presenta mejoras ante la forma
Anexo2-11
Anexo-2 Programacin en C
tradicional: permite depurar un programa utilizando el cdigo fuente original.
Tambin enlaza automticamente el cdigo objeto compilado junto con cada lnea
de el programa con el cdigo fuente correspondiente. Se puede controlar la
ejecucin del programa estableciendo los puntos de ruptura en el cdigo
fuente. Tambin se puede ejecutar un programa paso-a-paso, una sentencia cada
vez, y observar el contenido de la pila de llamada del programa.
Adems, la comunicacin con el depurador de Turbo C se lleva a cabo
utilizando las expresiones de C.
SUBCOMANDOS
EVALUATE.- Puede evaluar cualquier expresin legal de C. Pueden
utilizarse variables que estn definidas en el programa y que se estn
corrigiendo como parte de la expresin.
CALL STACK.- Visualiza los contenidos de la pila en el orden de
llamada de las diferentes funciones. Tambin visualiza el valor de
cualquier parmetro de funcin en el momento de la llamada.
FIND FUNCTION.- Esta opcin pide el nombre de la funcin y a
continuacin coloca el cursor en la primera lnea de la funcin.
REFRESH DISPLAY.- Refresca la pantalla. Cuando se produce
sobreescritura con la salida deseada por la ventana de usuario.
DISPLAY SWAPPING.- Permite controlar las condiciones bajo las cuales
la pantalla cambiar desde el editor a la pantalla de usuario y retorno.
Tiene tres acciones:
Smart.- Solamente si el cdigo generado obliga a generar en la
pantalla de salida.
Always.- Cada vez que se ejecute el cdigo.
None.- Nunca.
SOURCE DEBUGGING.- Especifica si la informacin de depuracin es
situada en el fichero .EXE generado por el enlazador y si esa
informacin es cargada por el depurador de Turbo C. Alternativas:
On.- La informacin del depurador es situada en el .EXE y
utilizada por el propio depurador.
Standalone.- La informacin es situada en el .EXE pero no
utilizada por el depurador.
None.- La informacin no es depositada en el fichero .EXE ni
usada por el depurador.
Break/watch
Add watch Ctrl-F7
Delete watch
Edit watch
Remove all watches
Toggle breakpoint Ctrl-F8
Clear all breakpoints
View next breakpoint
BREAK/WATCH.- Permite establecer puntos de ruptura y definir las
variables y expresiones que se pueden ver mientras se ejecuta el programa.
Ambas son parte del depurador.
Anexo2-12
Anexo-2 Programacin en C
SUBCOMANDOS
ADD WATCH.- Aade una expresin en la ventana de visualizacin,
presentado el valor que va tomando dicha expresin durante la ejecucin
del programa. La expresin se mostrar en un formato apropiado para su
tipo de dato.
DELETE WATCH.- Elimina una expresin de la ventana de visualizacin.
EDIT WATCH.- Edita el nombre de la expresin para su modificacin.
REMOVE ALL WATCH.- Limpia todas las expresiones de la ventana de
visualizacin.
TOGGLE BREAKPOINT.- Asigna o limpia un punto de ruptura (breakpoint)
en la lnea donde est el cursor.
Durante el tiempo que exista un breakpoint activo, la ejecucin del
programa en modo Debug se detendr en el momento de llegar a la lnea
que lo contenga.
CLEAR ALL BREAKPOINTS.- Elimina todos los puntos de ruptura del
programa.
VIEW NEXT BREAKPOINT.- Coloca el cursor en el siguiente punto de
ruptura que encuentre en el programa.
Anexo2-13
ANEXO 3
Editor de Turbo C
Anexo-3 Programacin en C
INTRODUCCIN
El funcionamiento de este editor es similar al programa WordStar de
Micropro. Contiene alrededor de cincuenta rdenes y es muy poderoso. Sin
embargo, no hay que aprender todas estas rdenes a la vez. Los tipos de
rdenes ms importantes son: La insercin, el borrado, el movimiento de
bloques, la bsqueda y la bsqueda con sustitucin. Despus de dominar estos
tipos de rdenes bsicas, se podr aprender el resto de las rdenes de editor
fcilmente y utilizarlas segn las necesidades.
Con algunas excepciones, todas las rdenes comienzan con el carcter
control (<Ctrl>). En muchas, este carcter se sigue por otros.
LLAMANDO AL EDITOR
Para llamar al editor, se usan las teclas de cursor para mover la barra
iluminada a Edit o simplemente pulsar <e>. Para dejar el editor, pulsar <F10>.
La lnea iluminada es la -lnea de estado del editor- e indica diversas
caractersticas del editor y el archivo que se est editando:
Line y Col.- Visualizan el nmero de lnea y la columna del cursor.
Insert.- Indica que el editor est en modo insertar; es decir, que al
introducir texto, Turbo C lo "insertar" en medio de lo que (de haber)
est en el editor. El modo opuesto es sobreescritura overwrite; en este
modo de operacin, el texto nuevo solapa al existente. Se puede conmutar
entre los dos modos pulsando <Ins>.
Indent (Sangrado).- Indica que est activa la autoindentacin
(autosangrado). Pulsando <Ctrl> <O> <I> se conmuta el modo de
indentacin.
Tab.- Dice que se pueden insertar tabuladores usando <Tab>. Pulsando
<Ctrl> <O> <T> se conmuta el modo tabulador.
Unindent.- Significa que cuando se teclee un espacio hacia atrs
(<backspace>) al principio de la lnea, el cursor se mover hacia la
izquierda un nivel de sangrado (indentacin) cada vez que se pulse. Se
puede cambiar esta caracterstica utilizando la orden <Ctrl> <O> <U>.
Cuando no est activado, el cursor retrocede un espacio cada vez que se
pulse la tecla de espacio sin tener importancia lo profundamente que se
haya realizado la sangra (indentacin).
Por ltimo, se visualiza el archivo que se est editando. Si se
visualiza un asterisco delante del nombre de archivo, significa que le
archivo se ha modificado. Turbo C usa el archivo por defecto NONAME.C.
COMANDOS DEL EDITOR DE TURBO C
A continuacin se presenta un resumen de los comandos contenidos en el
editor de turbo C. Se encuentran repartidos en bloques funcionales para
facilitar al lector la bsqueda y asimilacin de los mismos.
Anexo3-2
Anexo-3 Programacin en C
Comandos de movimiento del cursor Comandos de borrado e insercin
------------------------------------------ ------------------------------------------
Carcter izquierdo Ctrl-S or Left arrow Modo insercin on/off Ctrl-V or Ins
Carcter derecho Ctrl-D or Right arrow Inserta lnea Ctrl-N
Palabra izquierda Ctrl-A Borrar lnea Ctrl-Y
Palabra derecha Ctrl-F Borra hasta fin lnea Ctrl-Q Y
Lnea arriba Ctrl-E or Up arrow Borra carcter izqui. Ctrl-H or Backspace
Lnea abajo Ctrl-X or Down arrow Borra carcter Ctrl-G or Del
Scroll arriba Ctrl-W Borra palabra derecha Ctrl-T
Scroll abajo Ctrl-Z
Pgina arriba Ctrl-R or PgUp
Pgina abajo Ctrl-C or PgDn
Comandos de bloque Otros comandos
----------------------------------- --------------------------------------------
Marca inicio bloque Ctrl-K B Men principal Ctrl-K D or Ctrl-K Q
Marca fin de bloque Ctrl-K K Salvar y editar Ctrl-K S or F2
Marca una palabra Ctrl-K T Nuevo fichero F3
Copia bloque Ctrl-K C Fichero anterior Alt-F3
Mueve bloque Ctrl-K V Tabulador Ctrl-I or Tab
Borrar bloque Ctrl-K Y Modo tabulador Ctrl-O T
Lee bloque del disco Ctrl-K R Auto-indent on/off Ctrl-O I
Graba bloque en disco Ctrl-K W Restaura lnea Ctrl-Q L
Oculta o visualiza b. Ctrl-K H Sita marca de lugar Ctrl-K (0, 1, 2, or 3)
Imprime bloque Ctrl-K P Busca marca de lugar Ctrl-Q (0, 1, 2, or 3)
Mensajes de ayuda Ctrl-F1
Carcter de control Ctrl-P (prefijo)
Comandos de bsqueda Cancelar Ctrl-U or Esc
------------------------------------------ Ir comienzo de lnea Ctrl-Q S
Busca Ctrl-Q F Ir al fin de lnea Ctrl-Q D
Busca y sustituye Ctrl-Q A Principio pantalla Ctrl-Q E
Repite cuando encuentres Ctrl-L Final de la pantalla Ctrl-Q X
Principio de archivo Ctrl-Q R
Opciones de Bsqueda Final del archivo Ctrl-Q C
B Busca hacia adelante desde cursor Principio de bloque Ctrl-Q B
G Busca en todo el fichero Final del bloque Ctrl-Q K
L Busca en bloque actual ltima posicin curs. Ctrl-Q P
n (entero) Encuentra la n-ava ocurrencia Sangrar bloque Ctrl-K I
N Reemplaza sin preguntar (Ctrl-Q A) Quita sangrado bloque Ctrl-K U
U Encaja maysculas o minsculas Sale del editor F10
W Encaja slo palabras completas Mensaje error sobree. Ctrl-Q W
CORRESPONDENCIA DE PARES
Existen varios -delimitadores- en C que funcionan por parejas, tales
como { }, [ ] y ( ). En programas largos y muy complejos, a veces es difcil
encontrar el propio compaero delimitador. Es posible hacer que el editor
encuentre el delimitador compaero correspondiente automticamente.
Turbo C encuentra al delimitador compaero como ocurre con las
siguientes parejas de delimitadores:
{ }, [ ], ( ), < >, /* */, " ",
Para encontrar el delimitador correspondiente, colocar el cursor sobre
el delimitador que se desea corresponder, y teclear <Ctrl> <Q> <[> (para el
delimitador de delante) o <Ctrl> <Q> <]> (para el delimitador de atrs). El
editor mover el cursor al delimitador que corresponde. Unos delimitadores son
anidables y otros no. Los delimitadores anidables son { }, [ ], ( ), < >, y
algunas veces tambin los smbolos de comentario (cuando se permite una opcin
de comentario anidada). Si, por alguna razn, el editor no encuentra el
delimitador correspondiente, el cursor no se mover.
Anexo3-3
ANEXO 4
Unidades E/S
Anexo-4 Programacin en C
INTRODUCCIN
La funcin de las unidades de Entrada/Salida (I/O -input/output-
notacin anglosajona) es adaptar la informacin procedente del exterior para
que sea interpretable por el ordenador, as como adaptar la informacin
suministrada por el ordenador para que pueda ser trata por los perifricos.
Las unidades perifricas no forman parte de la unidad central del
ordenador de ah que, necesariamente, haya que habilitar el intercambio de
informacin entre la CPU y los perifricos.
Un ordenador puede disponer de varias unidades de E/S que, a su vez,
pueden controlar varios perifricos del mismo tipo. Las principales ventajas
obtenidas con su empleo son las siguientes:
1. La velocidad de trabajo de la CPU es muy superior a la de los
perifricos; en consecuencia, mediante las unidades E/S se
consigue la independencia entre ambas y el mejor rendimiento de
la CPU.
2. Los perifricos pueden tratar de distinta forma a la informacin,
incluso en unidades del mismo tipo esta caracterstica vara segn
los fabricantes. Mediante las unidades de E/S se pueden adaptar
muy diversos tipos de perifricos, independientemente de que sus
formatos sean distintos a los de comunicacin del propio
ordenador.
3. Las unidades de E/S tambin sirven de intermediarias entre las
lgicas binarias del ordenador y de los perifricos, que pueden
ser distintas.
TIPOS DE TRANSMISIN
Ilustr. 1 Las unidades de E/S se ocupan de adaptar CPU y perifrico
Una de las principales funciones de las unidades de E/S es la
transmisin de informacin entre la CPU y los perifricos. Esta transmisin
puede efectuarse, esencialmente, de dos formas distintas: en serie o en paralelo.
La transmisin en PARALELO trata de forma simultnea a todos los
elementos de una unidad de informacin; por ejemplo, enviando por ocho lneas
distintas los ocho bits de una palabra binaria (byte).
En la transmisin en SERIE la informacin es canalizada de forma
secuencial, es decir, por una sola lnea se envan, uno tras otro, todos los
Anexo4-2
Anexo-4 Programacin en C
bits de una palabra.
Ilustr. 2 A) Transmisin paralelo de una palabra de 8 bits
B) Transmisin en serie de una palabra de 8 bits
C u
a n
d o
s e
q u
ieren transmitir datos a distancias cortas, el modo que ms se utiliza es el
paralelo, lo que se debe a su mayor rapidez, ya que permite la transmisin
simultnea de varios bits de datos. Pero ello exige un nmero de lneas de
datos igual al nmero de bits que tenga la palabra de datos a enviar,
aadindole un determinado nmero de lneas que transportan las seales de
control encargadas de gobernar la transferencia.
La transmisin en paralelo es, un s misma, un modo de transmisin muy
fiable. Sin embargo, cuando la distancia que separa ambos sistemas tiene una
cierta importancia, la transmisin en paralelo se hace algo costosa, puesto
que la longitud de la lnea ha de multiplicarse por el nmero de bits, Adems,
se han de instalar varios amplificadores intermedios en cada lnea. Tambin
debe de considerarse la aparicin de muchos tipos de distorsiones al tenerse
que enviar muy rpidamente la informacin binaria que cambia continuamente a
travs de lneas muy prximas.
FORMATOS NORMALIZADOS DE E/S
En los ltimos aos se ha hecho un considerable esfuerzo para la
normalizacin de los formatos utilizados para establecer la comunicacin entre
microordenadores y entre estos y los perifricos de distintos fabricantes. El
fruto de ese esfuerzo ha sido la utilizacin masiva de dos tipos de interface
que han logrado imponerse en sus respectivas categoras:
Interface RS-232 tipo serie
Anexo4-3
Anexo-4 Programacin en C
Interface Centronics tipo paralelo
Este ltimo bsicamente orientado a las transferencias de datos del
ordenador hacia un perifrico de impresin.
Existen otras normas estandarizadas pero con aplicaciones ms
especficas, entre las del tipo paralelo se encuentra la IEEE 4888 muy
utilizada en instrumentos de medida. Mientras que en la comunicacin serie
destaca la RS-423 como posible sucesora de la RS-232.
TRANSMISIN SERIE ASNCRONA Y SNCRONA
Se llama sincronizacin al proceso mediante el que un emisor informa a
un dispositivo receptor sobre los instantes en que van a transmitirse las
correspondientes seales. En el proceso de sincronizacin pueden distinguirse
tres niveles:
- Sincronizacin a nivel de bit
Debe reconocerse el comienzo y el fin de cada bit.
- Sincronizacin a nivel de carcter o palabra
Debe reconocerse el comienzo y el final de cada unidad de
informacin, como puede ser un carcter o una palabra transmitida.
- Sincronizacin a nivel de bloque
Debe reconocerse el comienzo y el final de cada bloque de datos
(conjunto de palabras o caracteres).
La transmisin ASNCRONA consiste en acompaar a cada unidad de
informacin un bit de arranque (start) y otro de parada (stop). Esto se
consigue manteniendo la lnea a nivel 1, de tal forma que el primer 0 es el
bit de arranque y a continuacin se transmiten los bits correspondientes al
carcter (de 5 a 8 bits segn el cdigo utilizado), terminando la transmisin
con un 1, cuya duracin mnima sea entre 1 y 2 veces la de un bit. La lnea
se mantendr en este nivel hasta el comienzo de la transmisin del siguiente
carcter.
Anexo4-4
Anexo-4 Programacin en C
Ilustr. 3 Transmisin asncrona
La transmisin SNCRONA es una tcnica ms eficiente que la anterior y
consiste en el envo de una trama de datos (conjunto de caracteres) que
configura un bloque de informacin comenzando con un conjunto de bits de
sincronismo (SYN) y termina con otro conjunto de bits de final de bloque
(ETB). En este caso, los bits de sincronismo tienen la funcin de sincronizar
los relojes existentes tanto en el emisor como en el receptor, de tal forma
que stos controlan la duracin de cada bit y carcter ahorrando con respecto
al esquema anterior los bits de "start" y "stop" de cada carcter.
Ilustr. 4 Transmisin sncrona
SIMULTANEIDAD EMISIN-RECEPCIN
Una lnea de comunicacin tiene dos sentidos de transmisin que pueden
existir simultneamente o no. Por este motivo, existen los siguientes modos
de transmisin:
- Smplex (SPX)
La lnea transmite en un solo sentido sin posibilidad de hacerlo en el
otro. Esta modalidad se usa exclusivamente en casos de captura de datos
en localizaciones lejanas o envo de datos a un dispositivo de
visualizacin desde una computadora lejana. Dos ejemplos pueden ser la
captura de datos en estaciones meteorolgicas y la transmisin de
informacin a los sealizadores luminosos en las carreteras.
- Semidplex o half-dplex(HDX)
La lnea transmite en los dos sentidos pero no simultneamente.
Anexo4-5
Anexo-4 Programacin en C
- Dplex o full-dplex(FDX)
La lnea transmite en los dos sentidos simultneamente.
VELOCIDADES DE TRANSMISIN
Se denomina velocidad de transmisin a la cantidad de informacin enviar
por una lnea de transmisin en una unidad de tiempo.
Existen distintas unidades para la medida de la velocidad de
transmisin:
- Baudio
Es el nmero de estados que toma la seal enviada por unidad de tiempo.
- Bits/segundo
El nmero de bits de informacin enviados por segundo. Los valores de
una velocidad de transmisin son iguales cuando se transmite en un
nivel, y difieren cuando se realiza una transmisin por niveles
mltiples.
- Caracteres/segundo (CPS)
Es el nmero de caracteres transmitidos por segundo.
- Palabras/minuto (WPM)
Es el nmero de palabras transmitidas por minuto. Generalmente se
considera que una palabra tiene seis caracteres.
A continuacin se muestra la velocidad en bits por segundo y baudios de
una transmisin por niveles mltiples:
Ilustr. 5 Velocidad de transmisin de una seal
Anexo4-6
ANEXO 5
Puerto SERIE
NORMA RS-232-C
Anexo-5 Programacin en C
INTRODUCCIN
En 1969, la EIA (Asociacin de Industrias Electrnicas) - asociacin
americana -, los laboratorios Bell y los fabricantes de equipos de
comunicaciones, formularon cooperativamente y emitieron el EIA RS-232, para
normalizar el material y los procedimientos de transmisin en serie entre los
ordenadores y los perifricos mediante una red de transmisin. que casi
inmediatamente experiment revisiones menores, convirtindose en la RS-232-C.
El CCITT (Comit Consultatif International de Tlgraphie et de
Tlephonie), que agrupa a fabricantes y organismos oficiales (PTT) de varios
pases, relanz esta normalizacin a nivel mundial bajo el nombre de V.24.
En este anexo, se analizar esta conexin: la RS-232-C, que es la
conexin serie ms comnmente utilizada en los microordenadores.
Para su estudio, se distinguirn dos bloques bien diferenciados, que
permitirn comprender mejor la filosofa de una conexin:
Bloque I.- Norma asociada a la conexin. Hace referencia a un conjunto
de reglas que por convenio adoptan todos los fabricantes de
equipos para lograr su compatibilidad (ej.: se transmite en
tensin entre 15 V).
Bloque II.- Hardware asociado. Para realizar la conexin definida en el
bloque I es necesario una circuitera que la soporte (la
tarjeta insertada en el ordenador, y por tanto una direccin
determinada).
LA NORMA RS-232-C
Se debe especificar que la conexin RS-232-C fue desarrollada para un
nico propsito, establecido por su ttulo:
Conexin entre un Equipo Terminal de Datos y un Equipo de Comunicacin de Datos
empleando un Intercambio de Datos binarios en serie.
Cada palabra es significativa: describe la conexin entre un terminal
(Equipo Terminal de Datos, o DTE, ej. un ordenador) a un modem (Equipo de
comunicacin de Datos, o DCE) para la transmisin de datos en serie
1
.
EL CONECTOR
El conector en s, consta de 25 patillas, llamado DB-25, aunque en
muchos equipos se utilizan conectores de 9 patillas (DB-9). En la siguiente
ilustracin se muestran las patillas ms importantes del RS-232-C para
ordenadores, si bien, en algunos casos, el fabricante puede utilizar
cualquiera de las restantes.
1
Las reglas definidas a partir de este objetivo no impiden que se puedan realizar otros tipos de
conexiones. La norma RS-232-C se estableci para conectar con un modem, por lo que aparecen muchas patillas
que en otras aplicaciones no se emplean.
Anexo5-2
Anexo-5 Programacin en C
Ilustr. 1 Interconexin bsica RS-232
La funcin de cada una de las patillas, impuesta por la norma
2
, es:
-2- Transmisin de datos (TXD).- Transmite datos del DTE (ordenador)
hacia el DCE (modem).
-3- Recepcin de datos (RXD).- Transmite datos del DCE hacia el DTE.
-4- Peticin de envo (RTS).- Terminal de control. Es una salida de
propsito general del DTE.
-5- Dispuesto a enviar (CTS).- Terminal de control. Entrada de propsito
general del DTE. Normalmente el DCE la utiliza cuando desea indica el
estado de "preparado" en la adquisicin de datos para su posterior
transmisin.
-6- Dispositivo de datos listo (DSR).- Terminal de control. Entrada de
propsito general que indica al DTE que el DCE est encendido y listo
para fucionar.
-7- Circuito comn (SG).- Terminal de masa. Punto de referencia de todas
las tensiones de la conexin (es obligatoria).
-8- Deteccin de portadora (DCD).- Terminal de control. Salida de
propsito general usada habitualmente para indicar al DCE que el DTE
est encendido y listo para funcionar.
-20- Terminal de datos listo (DTR).- Terminal de control. Salida de
propsito general del DTE que suele emplearse para indicar al DCE que
el DTE est encendido y listo para funcionar.
-22- Indicador de llamada (RI).- Terminal de control. Entrada de
propsito general usada por el DCE para expresar al DTE que se est
recibiendo una llamada telefnica. Esta entrada, por regla general, slo
tiene sentido cuando el DCE es un modem.
2
Cada una de las patillas del conector RS-232-C tiene una funcin especificada por la norma. Hay
unos terminales por los que se transmiten y reciben datos, y otros que controlan el establecimiento, flujo
y cierre de la comunicacin (los terminales de control manejan las seales de protocolo)
Anexo5-3
Anexo-5 Programacin en C
Los nombre de las seales y su nmero de terminal son exactamente los
mismos para el DTE y para el DCE. Son, sin embargo, exactamente opuestos
funcionalmente, una salida en el DTE es una entrada en el DCE (y viceversa).
Para que exista flujo de datos entre dispositivos RS-232 nicamente
sern imprescindibles las patillas 2 (TXD), 3(RXD) y 7 (Masa). Las dems
podrn ser eliminadas, segn el caso, ya que nos proporcionan los diferentes
estados de los dispositivos conectados.
A continuacin, se muestra la relacin ente las patillas del DB-25 y el
DB-9 usado por muchos equipos:
SEAL Patilla DB-9 Patilla DB-25
DCD 1 8
RXD 2 3
TXD 3 2
DTR 4 20
SG 5 7
DSR 6 6
RTS 7 4
CTS 8 5
RI 9 22
Obsrvese que la nica patilla que coincide es la nmero 6, DSR, y
adems los terminales 2 y 3, TRANSMISIN y RECEPCIN, estn cambiadas.
NIVELES LGICOS DEL RS-232-C
Los datos se transmiten con lgica negativa, es decir, un voltaje
positivo en la conexin representa un "0", mientras un voltaje negativo
representa un "1".
Definiciones lgicas para las salidas RS-232-C .- El "1" est asignado
a niveles negativos de voltaje, y el "0" a los positivos. Para
garantizar un "0", una patilla de salida debe mantener un voltaje entre
+5 y +15 voltios. Similarmente, un "1" garantizado debe estar entre -5
y -15 voltios. La "banda muerta" entre +5 y -5 se conoce como la regin
de transicin, donde los niveles lgicos no estn definidos. Esto
significa que cualquier salida entre +5 y -5 voltios puede interpretarse
ambigamente como "0" o "1".
Definiciones lgicos para las entradas RS-232-C .- La nica diferencia
entre esta definicin y la de las salidas es el ancho de la regin de
transicin. La zona lgica indefinida de la entrada es nicamente de 6
voltios, mientras el rea correspondiente para una salida es de 10
voltios. Esta diferencia entre voltajes se conoce como margen de ruido.
Anexo5-4
Anexo-5 Programacin en C
Ilustr. 2 A) Definiciones lgicas para las salidas RS-232-C
B) Definiciones lgicas para las entradas RS-232-C
En cuanto a los niveles lgicos de control y acoplamiento:
Las ENTRADAS estn habilitadas cuando son positivas y deshabilitadas
cuando son negativas.
Las SALIDAS estn afirmadas cuando son positivas e inhibidas cuando son
negativas.
Nota: En el mercado existen circuitos integrados que permiten la conversin entre niveles
TTL y niveles RS-232. Entre ellos podemos destacar:
75188, 1488
75189, 1489.- Convertidor RS-232 a TTL
DS14C88, DS1489A.- Convertidor RS-232 a CMOS
Transceptor MAX 232.- con una tensin de alimentacin de 5 v se consiguen (por medio de
unos convertidores internos) niveles RS-232
ESPECIFICACIONES ELCTRICAS RS-232-C
Las dos especificaciones elctricas ms importantes
3
del RS-232-C son:
1.- A la hora de conexionar, el excitador de un circuito de intercambio
ha de disearse para soportar un circuito abierto, un cortocircuito en
dicho hilo de intercambio en el cable de conexin y cualquier otro
conductor en dicho cable... incluyendo la masa de seal, sin causar
daos a s mismo o a sus dispositivos asociados. Cualquier terminal se
puede conectar a cualquier otro en cualquier momento sin causar daos,
siempre que los dispositivos contengan verdaderos conectores RS-232-C.
2.- La segunda especificacin clave, requiere que el transmisor siempre
est a un voltaje negativo cuando no se estn transmitiendo datos. Como
3
Tanto estas reglas como las de los niveles lgicos, son especificaciones transparentes al
programador, pues de su cumplimiento ya se han ocupado los fabricantes de las tarjetas.
Anexo5-5
Anexo-5 Programacin en C
los datos deben transmitirse por la patilla 2 o la 3, probando estas 2
patillas podemos asegurarnos de si el dispositivo es un DTE o un DCE.
CONEXIN DTE - DTE
Esta es la conexin ms problemtica entre dispositivos con RS-232-C,
debido precisamente a que la conexin RS-232 est diseada para conectar un
terminal, DTE, con un modem, DCE. En cuanto nos apartamos de este diseo las
reglas bsicas parecen fallar, pero el problema se soluciona mediante el
conocimiento de las normas anteriores, sobre todo en lo que se refiere a la
funcin de cada una de las patillas disponibles.
Es evidente que no se pueden interconectar dos dispositivos DTE
siguiendo el esquema de la ilustracin 1. Lgicamente si conectamos salidas
con salidas y entradas con entradas, no nos proporcionaran control de los
dispositivos y, por supuesto, ningn flujo de datos.
La nica conexin lgica
4
ser conectar salidas con entradas, tal y
como se muestra en la ilustracin siguiente:
Ilustr. 3 Conexin DTE - DTE
La patilla 8 no tiene una salida correspondiente. En caso de necesidad,
es corriente puentearla con la patilla 20.
SOPORTE HARDWARE
En este apartado, se estudia la estructura, caractersticas y
funcionamiento de la tarjeta que controla la comunicacin RS-232-C.
Est claro, que para que cada terminal definido cumpla la funcin
especificada por la norma, debe haber un circuito hardware anterior a stas
que las controle. Este circuito constar de una serie de elementos situados
a partir de una direccin base, la cual utilizar el ordenador para su control
y programacin segn se indicar. El elemento es:
La UART (Universal Asynchronous Receiver/Transmitter)
4
Cuando aparecen otras configuraciones distintas a la conexin DTE-DCE, para la que no se ha
pensado la norma, debemos establecer la conexin fsica pensando en la funcin de las patillas, y
posteriormente realizar el PROGRAMA de control segn esta funcin.
Anexo5-6
Anexo-5 Programacin en C
UART
Las funciones bsicas E/S asncronas estn integradas dentro del
circuito denominado Transmisor/Receptor Asncrono Universal o UART.
Funcionalmente, la UART rene una seccin de TRANSMISIN y una seccin
de RECEPCIN, en las cuales se llevan a cabo las siguientes funciones:
- Convierte los octetos (8bits) que recibe del procesador en paralelo,
en una corriente en serie de 8 bits, y viceversa, las corrientes en
serie de 8 bits las convierte en octetos en paralelo para entregarlas
al procesador.
- Aade los necesarios bits de comienzo ("start"), parada ("stop") y
paridad a cada carcter que va ser transmitido y los elimina de los
caracteres que se reciben.
- Se asegura de que los bits son enviados a la velocidad apropiada.
Calcula el bit de paridad de los caracteres recibidos e informa de los
errores detectados.
Adems hay una seccin de CONTROL Y ESTADO, que, entre otras cosas,
recibe el estado lgico de las patillas de control o acoplamiento, informando
al procesador del estado de stas. Por otro lado, cualquier programa mediante
llamada a esta seccin, puede cambiar el estado de las seales de acoplamiento
de salida. La entrada/salida asncrona en serie y el protocolo RS-232 estn
unidos inseparablemente.
La UART es, bsicamente, el hardware asociado de toda la norma anterior.
Como puede observarse en la figura siguiente su control resulta muy fcil.
Dependiendo de nuestra aplicacin, nos interesar controlar determinadas
patillas y actuar de determinada forma, por lo que mediante un programa en el
ordenador que controle a la UART solucionaremos el problema.
Nota: Se est particularizando para la RS-232-C, pero la forma de operar es semejante para
cualquier conexin del ordenador. Si conocemos su soporte hardware y las direcciones
asociadas, todo se reduce a crear un programa controlador que lea y escriba en esas
direcciones, y acte en consecuencia.
Ilustr. 4 Configuracin bsica de la National 8250
Anexo5-7
Anexo-5 Programacin en C
A continuacin se repasa el funcionamiento bsico de la UART INS8250 de
National, que es la utilizada en el IBM PC. Antes de comenzar debe efectuarse
una pequea aclaracin:
Se marcan los nombre de algunas entradas y salidas RS-232 con un superrayado para indicar
que la lgica est invertida. Si bien, esta inversin no es detectable por el programador, ya que
las seales se vuelven a invertir en los controladores EIA, que se interponen entre la UART y el
conector fsico del interface.
Tal y como se muestra en la imagen anterior, el 8250 requiere dos
interfaces bsicos (bus de E/S del sistema y la E/S RS-232) y un reloj.
El circuito est conectado a los 8 bits de menor peso del bus de datos
de la CPU a travs de las lneas D0 a D7. Es sta la ruta que siguen los datos
para entrar y salir de la UART. Las operaciones de lectura y escritura quedan
diferenciadas por las lneas (de habilitacin) de entrada y salida de datos
y . Adems, la 8250 contiene varios registros internos,
direccionables INDIVIDUALMENTE por medio de las tres lneas de seleccin
A0 - A2.
La transmisin de un byte es una operacin de tres etapas:
1) La CPU coloca el byte de datos de salida en las 8 lneas
D0 - D7
2) El nmero de registro del buffer de transmisin se pasa a las
entradas A0 - A2 de seleccin de registros
3) La lgica de las lneas de datos y desplazan el byte
desde D0 - D7 al buffer de transmisin. La 8250 mueve el byte
desde el registro del buffer al registro de desplazamiento del
transmisor, cuando este ltimo est vaco.
Las etapas de recepcin y la lectura y escritura de cualquier otro
registro de la 8250 es idntica a las de transmisin, la nica diferencia
estriba en las lneas de seleccin A0 - A2. El punto final del interface con
e bus E/S del sistema es la lnea marcada INTRPT, o interrupcin. Esta salida
se activa cada vez que surge una condicin para lo cual la 8250 genera una
interrupcin.
La UART va actuar de acuerdo al contenido de sus registros internos, por
lo que controlando (leer y escribir en ellos) su contenido, controlamos la
conexin. En la mayora de las implementaciones, los registros de la UART se
direccionan en puertos E/S o direcciones de memoria consecutivas. Normalmente
el COM1 (puerto de comunicaciones serie RS-232-C n 1) se encuentra en la
direccin 3F8h, y el COM2 (puerto serie RS-232-C n 2) en 2F8h (por lo
general, los ordenadores disponen de dos puertos serie). Se tomar estas
direcciones como DIRECCIONES BASE, ya que a partir de ellas se situarn los
dems registros.
Anexo5-8
Anexo-5 Programacin en C
Ilustr. 5 Situacin de los registros en la UART
A continuacin se explican brevemente los registros de la UART:
La UART dispone de 11 registros, aunque realmente slo existen 8
registros reales, y nicamente tenemos 3 lneas fsicas de seleccin para
direccionarlos. El registro transmisor y el receptor son versiones
lectura/escritura de la misma direccin. Y adems, para extender el
direccionamiento su usa un pequeo truco: cuando el bit 7 del registro de
control de lnea, LCR (Line Control Register) es "1", los registros 0 y 1 se
transforman en los bytes bajo y alto del latch divisor de velocidad en
baudios.
REGISTRO 0 (y 0)
5
- Registro del buffer de receptor - RBR - (ENTRADA)
El bit 7 del LCR debe estar a "0". Registro de lectura. Este registro
contiene el ltimo carcter recibido. Si el registro no es ledo, por
la CPU, antes de que el siguiente carcter sea recibido, el carcter
original se perder.
- Registro del buffer transmisor - THR -
El bit 7 del LCR debe ser "0". Registro de escritura. Cuando se escribe
un byte en este registro se produce su serializacin y transmisin en
el formato y velocidades actuales. Tambin llamado registro de
retencin.
5
La UART emplea el mismo registro para recibir y enviar datos. Hay que saber en todo momento
cuando se va a enviar y cuando se va a recibir para no perder la informacin. Debe sincronizarse la
comunicacin (normalmente empleando interrupciones).
Anexo5-9
Anexo-5 Programacin en C
REGISTRO 1
6
- Registro de activacin de interrupciones - IER -
El bit 7 del LCR debe ser "0". Registro de lectura/escritura. Los bits
de este registro permiten activar los cuatro tipos de interrupciones
soportados por la 8250. Cada interrupcin queda activada cuando su bit
respectivo vale "1".
Bit 0 (RxRDY Received Data Available Interrupt)
Si est a "1", se genera una interrupcin cuando hay un byte
disponible para lectura en RBR.
Bit 1 (TBE Transmitter Holding Register Empty Interrupt)
Si este bit est a "1", se genera una interrupcin cuando se
desplaza un byte desde el THR al registro de desplazamiento; es
decir, tan pronto como la 8250 pueda aceptar otro byte para
transmisin.
Bit 2 (Receiver Line Status Interrupt)
Si este bit est a "1", se genera una interrupcin cuando se
detecta un error de paridad o una condicin BREAK en el receptor
durante la llegada de un byte.
Bit 3 (Modem Status Interrupt)
Si este bit est a "1", se genera una interrupcin cuando cambia
cualquiera de las entradas RS-232. Suele llamarse entrada RS-232.
Bit 4-7 Siempre 0
REGISTRO 2
- Registro de identificacin de interrupciones - IIR -
Registro de SOLO LECTURA. Cuando aparece una interrupcin, se utiliza
este registro para identificar su procedencia exacta. Un "0" en el bit
0 significa que la interrupcin est pendiente; los bits 1 y 2
identifican la interrupcin segn se muestra en la siguiente tabla.
Bit 2 Bit 1 Bit 0 Prioridad Identificacin de interrupcin
0 0 1 Ninguna Ninguna
1 1 0 0 Error de serializacin
1 0 0 1 dato recibido
0 1 0 2 Buffer de transmisin vaco
0 0 0 3 Entrada de RS-232
Obsrvese que todas ellas tienen una prioridad: cuando hay una
interrupcin pendiente, no se informan otras con igual o menor prioridad
(es decir, quedan bloqueadas). Esta prioridad NO ES PROGRAMABLE.
6
Para operaciones de muestreo (sin utilizar las interrupciones) en la UART debe inhibirse toda
seal de interrupcin generada por la UART hacia el dispositivo de tratamiento de interrupciones (PIC) o
bien inhabilitar -enmascarar- (dentro del PIC) las originadas en el COM1.
Anexo5-10
Anexo-5 Programacin en C
La prioridad mayor es 0.
Ejemplo del mecanismo de interrupciones:
Se supone una conexin PC-perifrico. El PC enva un dato al perifrico
y espera su respuesta. Qu ocurre a continuacin?. Cuando el PC enva
el dato, se va a activar una interrupcin que indica a la UART del PC
que puede transmitir otro dato, pero como se espera recibir una
respuesta, nuestro programa inhabilitar dicha interrupcin.
Pasar el tiempo y el perifrico enviar su respuesta, momento en el
cual se activa la interrupcin de dato recibido. En este momento, el
programa del PC dejar lo que est haciendo y saltar a la rutina de
tratamiento de interrupciones del puerto serie. Una vez en ella, el PC
identificar que tipo de interrupcin particular de la UART se ha
producido (dato recibido), leyendo en el registro identificador de
interrupciones, y actuando en consecuencia segn lo hayamos programado.
REGISTRO 3
7
- Registro de control de lnea - LCR -
Tambin denominado formato de datos. Registro de LECTURA/ESCRITURA. el
significado de sus bits es el siguiente:
Bit 0-1 Nmero de bits de datos:
"0" "0" = 5 bits
"0" "1" = 6 bits
"1" "0" = 7 bits
"1" "1" = 8 bits
Bit 2 Nmero de bits de STOP:
"0" = 1 bit
"1" = 2 bits
Bit 3-5 Paridad:
"0" "0" "0" = Ninguna
"0" "0" "1" = Impar
"0" "1" "1" = Par
"1" "0" "1" = MARK
"1" "1" "1" = SPACE
La paridad MARK significa que el bit de paridad es siempre "1"
La paridad SPACE significa que el bit de paridad es siempre "0"
Bit 6 (Break)
Cuando est a "1", este bit fuerza al transmisor a adoptar un
estado lgico "0". El transmisor permanece incondicionalmente en
este estado hasta que se escribe un "0" en este bit.
Bit 7 (DLAB -bit de acceso al latch divisor)
Este bit no tiene nada que ver con el formato; permite extender
el nmero de registros direccionados con las tres lneas de
control. Cuando este bit est activo, los registros 0 y 1
7
El registro 3 es fundamental para establecer la conexin con xito. Si los equipos que se
conectan no emplean el mismo formato de datos, no se entenderan.
Anexo5-11
Anexo-5 Programacin en C
(transmisor/receptor y activacin de interrupcin) se transforman
en los bytes de menor y mayor peso de los registros del latch
divisor.
REGISTRO 4
- Registro control de modem - MCR -
Tambin llamado control de salidas RS-232. Este registro controla el
estado de las dos salidas RS-232: DTR y RTS.
Adems de los terminales DTR y RTS, se dispone de otras dos salidas de
propsito general (GPO1 y GPO2) para usos que no sean RS-232.
El registro contiene tambin un bit que realiza un test de bucle inverso
(loopback) al cual se conectan temporalmente salidas y entradas
complementarias. De este modo, se puede comprobar la continuidad del
sistema completo cambiando una salida, y comprobando a continuacin si
el cambio ha quedado reflejado en la entrada complementaria.
Bit 0 (DTR)
Cuando se escribe un "1" es este bit se activa la salida Data
Terminal Ready de la 8250.
Bit 1 (RTS)
Cuando se escribe un "1" en este bit se activa la salida Request
To Send de la 8250.
Bit 2 (GPO1)
Se trata de la primera de las dos salidas definibles por el
usuario.
Bit 3 (GPO2)
Es la segunda salida definible por el usuario. En el adaptador
serie IBM debe estar a "1" para permitir la generacin de
interrupciones.
Bit 4 (Loopback local)
Cuando este bit est a nivel alto, sucede lo siguiente:
1) La salida del transmisor se asigna el estado lgico "1"
2) La entrada del receptor se desconectar.
3) La salida del registro de desplazamiento del transmisor se
conecta directamente con el registro de desplazamiento del
receptor.
4) Las cuatro entradas de control RS-232 se conectan directamente
con las salidas de la forma siguiente:
Estas conexiones facilitan un mtodo sencillo para comprobar las
principales funciones de la UART. Los datos escritos por el
registro transmisor aparecen inmediatamente en el receptor. Las
interrupciones son completamente operativas y se generan
normalmente con entradas RS-232, las cuales a su vez, se pueden
Anexo5-12
Anexo-5 Programacin en C
generar escribiendo simplemente en el registro de control de
salida RS-232 (registro 4)
Bit 5-7 Siempre "0"
REGISTRO 5
- Registro estado de lnea - LSR -
Tambin llamado estado de serializacin. Este registro informa del
proceso de serializacin, incluyendo la deteccin de Break, errores de
recepcin, y actividad de los registros de transmisin y recepcin.
Las condiciones de error que informan los bits 1-4 generan una
interrupcin cuando est a nivel alto el bit 2 del registro de
activacin de interrupciones.
Bit 0 (RxRDY)
Este bit est a "1" cuando se ha solapado un byte entrante y
transferido al buffer del receptor. El bit queda anulado cuando
se lee.
Bit 1 (Sobreescritura del receptor)
Cuando este bit est a "1" se indica que un byte del buffer del
receptor acaba de ser borrado por la entrada de un nuevo byte; el
primer byte se pierde. El bit se anula por lectura del registro.
Bit 2 (Error de paridad)
Se activa cuando el bit de paridad del byte recibido no coincide
con la paridad debida segn el registro de formato de datos. El
bit queda anulado cuando se lee.
Bit 3 (Error de trama)
Cuando est a "1" indica que, tras ensamblar un byte recibido con
su bit de STOP, ste era incorrecto. El bit queda anulado cuando
se lee.
Bit 4 (Deteccin de Break)
Aparece un "1" en este bit cada vez que el receptor detecta una
condicin ESPACIO por un perodo mayor de una SDU. Una SDU es el
"paquete" fsico que se enva por la lnea de comunicaciones: los
bits de START, de datos, de paridad (cuando existen) y de STOP.
Se le llama unidad de datos serie (Serial Data Unit). El bit queda
anulado cuando se lee.
Bit 5 (Buffer de transmisor vaco)
El bit TBE se activa para informar que se ha desplazado un byte
desde el buffer del transmisor (registro de retencin) al registro
de desplazamiento. Si se olvida consultar este bit antes de
escribir en el registro del buffer del transmisor, puede llegar
a borrarse un byte que estuviese ya instalado en l. Esta
situacin, conocida como sobreescritura del transmisor. no es
informada por la 8250.
Es importante comprender que aunque el bit TBE advierta que la
8250 est lista para aceptar otro byte de transmisin, esto no
implica que se haya completado la transmisin del byte anterior.
Anexo5-13
Anexo-5 Programacin en C
REGISTRO 6
- Registro de estado del modem - MSR -
Registro de lectura. Tambin es llamado estado de entrada RS-232.
Los bits 0-3 de este registro informan si se ha producido algn cambio
de estado de su patilla respectiva RS-232. La aparicin de un "1" en
cualquiera de estos bits indica que la entrada ha sido modificada desde
la ltima vez que se ley. La lectura del registro limpia los bits 0-3.
Los bits 4-7 informan sobre el estado absoluto de sus respectivas
entradas RS-232. Debido a la lgica invertida, cuando aparece un "1" en
estos bits se indica normalmente la presencia de una tensin positiva
en el interface. Adems de sus obligaciones habituales, los bits 4-7
toman significados especiales durante el test loopback (ver bit 4 del
registro de "control de modem").
Bit 0 Cambio en la lnea CTS.
Bit 1 Cambio en la lnea DSR.
Bit 2 Este bit se activa en el flanco de subida del pulso de timbre
telefnico que recibe el modem.
Bit 3 Cambio en la lnea DCD.
Bit 4 Estado de la lnea CTS.
Bit 5 Estado de la lnea DSR.
Bit 6 Estado de la lnea RI.
Bit 7 Estado de la lnea DCD.
Adems los bits 4-7 durante la comprobacin loopback (cuando est alto
el bit 4 del registro "control del modem"). Informan sobre el estado
actual de las salidas RS-232. La asignacin es como sigue:
Bit 4 CTS = RTS; Bit 5 DSR = DTR; Bit 6 DCD = GPO2; Bit 7 RI = GPO1
REGISTRO 7
Este registro no tiene funcin reconocida. De hecho, puede usarse para
cualquier propsito, sin que ello afecte a la UART.
(pseudo) REGISTROS 8 y 9
- Latch divisor de velocidad en baudios -
El registro 8 contiene el byte menos significativo del latch y el
registro 9 el byte ms significativo.
Cuando se pone en alto el bit 7 del registro "control de lnea", los
registros 0 y 1 se transforman en los bytes menos significativo (LSB)
y ms significativo (MSB), respectivamente, del latch divisor. El reloj
de referencia del 8250 se divide por un entero de 16 bits contenido en
los registros 8 y 9. La frecuencia resultante se utiliza como reloj
maestro para controlar la lgica del transmisor y opcionalmente (por
medio de una conexin externa), la del receptor. Este reloj maestro se
divide a su vez por 16 para generar el reloj de baudios, que controla
la velocidad de transmisin y recepcin de los datos. El divisor para
cualquier velocidad puede calculase segn la frmula:
Anexo5-14
Anexo-5 Programacin en C
En la siguiente tabla se muestran los divisores (en hexadecimal) de las
velocidades ms populares:
DIVISOR HEXADECIMAL
BAUDIOS
DESEADOS
Con un cristal de cuarzo de:
1.8432 MHz 3.072 MHz
50 0900 0F00
75 0600 0A00
110 0417 06D1
134,5 0359 0594
150 0300 0500
300 0180 0280
600 00C0 0140
1200 0060 00A0
1800 0040 006B
2000 003A 0060
2400 0030 0050
3600 0020 0035
4800 0018 0028
7200 0010 0016
9600 000C 0014
19200 0006 000A
38400 0003 0005
56000 0002 --
La UART soporta el control de la RS-232 mediate el contenido de los 11
registros anteriores. Conociendo la direccin de estos registros, se puede
leer y escribir en ellos, resultando relativamente fcil controlar la
comunicacin.
En muchos casos se conoce la norma de la comunicacin, pero no las
direcciones de los distintos elementos del perifrico - tarjeta conectada al
PC -, por esto el fabricante realiza el HARDWARE para controlar las
distintas funciones de las patillas segn la norma y adems proporciona el
SOFTWARE de control asociado a esa tarjeta que sin ella sera imposible la
programacin. En estos casos, slo se trabaja con librera proporcionadas, las
cuales se encargan de establecer el protocolo y realizar la comunicacin
haciendo transparente al usuario toda la parte HARDWARE del interface.
Anexo5-15
ANEXO 6
Puerto PARALELO
NORMA CENTRONICS
Anexo-6 Programacin en C
INTRODUCCIN
La comunicacin en paralelo se produce cuando todos los bits de un
carcter se transmiten al mismo tiempo. La transmisin en paralelo es
SNCRONA, pero no utiliza un reloj de transmisin como en el caso serie, sino
que usa una seal de "strobe" para indicar al receptor que debe leer el dato
enviado por el puerto paralelo. Para este tipo de transmisin se necesita un
cable con, al menos, tantas lneas de transmisin como bits se quieren
transmitir simultneamente, ms la lnea de strobe. Este tipo de transmisin
tiene dos usos principales:
- Transmisin de datos a alta velocidad (Ej. una impresora)
- Adquisicin de datos en procesos de control (los bits se utilizaran
como interruptores)
El interface paralelo utilizado comnmente en los PCs sigue la
especificacin CENTRONICS, utilizando un conector de 25 pines.
NORMA CENTRONICS
No hay un estndar real para el interface paralelo, al igual que en
otros casos, se ha aceptado por la mayora de los fabricantes, la
especificacin realizada por IBM para sus ordenadores PC/XT/AT, convirtindose
en el estndar de hecho.
EL CONECTOR
Anexo5-2
Anexo-6 Programacin en C
El protocolo de esta norma se basa en utilizar las siguientes lneas:
- 7 lneas para transmitir el cdigo ASCII del carcter.
- 1 lnea para acompaar 1 bit de paridad para detectar errores.
- 7 lneas para el control de impresora.
Los terminales D0 - D7 del conector soportan los 8 bits del informacin,
7 del cdigo ASCII y el octavo de paridad. Las restantes seales tiene las
funciones que se comentan:
Esta seal la activa el ordenador (nivel bajo) cuando por las lneas
D0 - D7 est enviando un dato vlido para imprimir.
Cuando se activa se inicializa la impresora y borra todos los datos que
podra contener su buffer de entrada.
Se activa cuando se detecta un error en la impresora
Cuando la activa el ordenador manda a la impresora escribir una lnea
en blanco.
La activa la impresora para indicarle al ordenador que ha recibido los
datos enviados.
La activa la impresora cuando est imprimiendo y trata de advertir al
ordenador que hasta que no termine no la enve ms datos.
La activa la impresora para detener al ordenador, pues se ha detectado
falta de papel.
El protocolo de comunicacin es sencillo: el ordenador coloca los 8 bits
de los datos en las 8 lneas de datos. Luego manda un impulso en la lnea
llamada de comprobacin ("strobe"), lo que significa: << ahora la impresora
puede tomar en consideracin los 8 bits que forman un carcter >>. Luego la
impresora trata este carcter. Mientras dura el tratamiento, manda corriente
a la lnea de ocupacin ("busy"), indicando de esta forma que << est ocupada
y no debe mandarse otro de carcter >>. Cuando termina el tratamiento del
carcter manda a la lnea "ack" la seal de "acuse de recibo" ("acknowledge")
y termina con la seal de "ocupacin". De esta manera se informa al ordenador
que ya puede mandar el carcter siguiente.
Para sealar algn suceso particular se utilizan otras lneas. Por
ejemplo, cuando la impresora detecta que se le acaba el papel, lo indica al
ordenador mandando una seal (un nivel de tensin) por uno de los terminales.
ESPECIFICACIONES ELCTRICAS DEL PUERTO PARALELO
La salida de la impresora puede considerarse como un puerto de E/S
Anexo5-3
Anexo-6 Programacin en C
general y conectarse a cualquier aparato que respete las especificaciones
elctricas.
El puerto paralelo consta de 12 salidas digitales programables, de 5
entradas digitales y varios terminales de masa. Los niveles con los que se
trabajan son TTL con lgica positiva. Si se habla en trminos electrnicos,
un nivel alto ("1" lgico) se encuentra muy prximo a los 5 voltios, mientras
que un nivel bajo ("0" lgico) estara casi a 0 voltios. El hecho de referirse
a valores aproximados es fruto de la incertidumbre existente en el circuito.
Adems de la tolerancia habitual de los semiconductores, debe tenerse en
cuenta que segn el integrado de salida existente en la tarjeta paralelo de
el ordenador (rpido, bajo consumo, etc...) as sern sus valores tpicos de
trabajo. En cualquier caso, el nivel lgico "1" siempre debe proporcionar al
menos 3,8 voltios, lo que establece por tanto el primer valor de referencia.
Dicho con otras palabras, el primer requisito del diseo es que todos sus
componentes puedan funcionar con una tensin igual o superior a 3,8 voltios
e inferior a 5 voltios.
Un segundo requisito, ms crtico, es el del consumo. La seal que
aparece en cada lnea compatible TTL es ciertamente dbil y no admite cargas
que impliquen consumos apreciables. cada lnea puede suministrar alrededor de
unos 2,6 mA y absorber hasta 24 mA.
Destacar que las salidas del puerto quedan "atrancadas", es decir,
mantienen su estado hasta que se modifique. Esto puede considerarse una
ventaja, ya que no se requiere de un tratamiento de "refresco"
(actualizaciones continuas) para mantener un valor concreto a la salida.
RECOMENDACIONES
Debido a la fragilidad (por sus caractersticas elctricas) que posee
el puerto paralelo es recomendable utilizar una circuitera anexa que permita
la adaptacin o aislamiento de este respecto a la aplicacin conectada en l,
que impida cualquier deterioro interno que ocasione un mal funcionamiento del
sistema.
En la siguiente ilustracin se presenta un esquema orientativo de como
puede protegerse el puerto tanto de la informacin enviada desde el PC hacia
la aplicacin como viceversa. Esto es as gracias a dos circuitos integrados
que trabajan en modo de registros de almacenamiento (latch) con los siguientes
terminales de control:
. Terminal 11 = de habilitacin (Enable) (Cuando tiene un "0" mantiene
el ltimo valor guardado. Con "1" permite la carga del registro)
. Terminal 1 = control de salida -triestado- (Control output) (con "0"
el contenido del registro est presente en la salida. Cuando est a
nivel alto existe un estado del alta impedancia)
Cabe destacar que estos C.I. empleados a modo de Interface pueden
encontrarse tanto en la familia TTL (74373) como en CMOS (40373) siendo la
situacin (y funcin) de todos los terminales (incluidos los de control) la
misma en ambos casos.
Anexo5-4
Anexo-6 Programacin en C
Ilustr. 2 Proteccin del puerto paralelo
A continuacin se muestran dos circuitos de proteccin que pueden ser
utilizados en el desarrollo de aplicaciones orientadas al puerto paralelo:
Ilustr. 3 Aplicacin con latch
Anexo5-5
Anexo-6 Programacin en C
Ilustr. 4 Aplicacin con optoacopladores
Para la aplicacin con optoacopladores debe indicarse que generalmente
se trabajar con encapsulados de 16 pines que contienen en su interior un
cudruple optoacoplador (4 unidades diodo-transistor) como por ejemplo el
ISQ74.
Tambin se pueden encontrar versiones con par Darlington que permiten
utilizarse en aplicaciones en las que el factor consumo es importante.
PROGRAMACIN DIRECTA DEL PUERTO PARALELO
La aplicacin ms comn del puerto paralelo, y para la que fue
inicialmente diseada, es la comunicacin con las impresoras, y por lo tanto
era una comunicacin unidireccional (de salida).
Cuando IBM diseo sus modelos de la gama PS2 perfeccion esta interface,
contemplando dos modos de operacin: el modo compatible y el modo extendido.
El modo compatible es exactamente igual a lo anteriormente explicado
(unidireccional y compatible con la interface clsica definida). El modo
extendido permite transferencias bidireccionales -entrada y salida de datos-.
Mediante un registro adicional - ver ilustracin n 5 - se selecciona
el modo de operacin:
- Compatible
- Extendido
En caso de trabajar en modo extendido, permite programar los registros
de datos y control como registros de escritura (salida de datos) o lectura
(entrada de datos).
Sin embargo cuando se realiza la programacin del puerto paralelo
generalmente el diseador de la aplicacin opta por la programacin en modo
compatible para permitir su funcionamiento en toda la gama de ordenadores
compatibles. Por ello, la informacin que aqu se recoge est orientada hacia
la consideracin de que el registro de datos es unidireccional. Aunque, como
Anexo5-6
Anexo-6 Programacin en C
se ver ms adelante, este hecho no impide la comunicacin bidireccional (por
ejemplo, con otro PC).
Ilustr. 5 Registro de configuracin del puerto paralelo
LOS PORTS I/O DEL PUERTO
Se pueden conectar hasta tres puertos paralelos al PC, ya que hay
reservadas tres zonas en el espacio de direcciones de I/O (entrada/salida) del
PC para puertos paralelos:
Port Puerto
3BCh - 3BFh Puerto paralelo en la tarjeta MDA
378h - 37Fh Primer puerto paralelo
278h - 27Fh Segundo puerto paralelo
Las direcciones de Ports de los puertos de la tabla anterior no se
listan en orden numrico ascendente intencionadamente, sino que se indicaron
en el orden en el que la BIOS
1
busca los puertos al arrancar el sistema. Ya
que la BIOS primero ha de analizar los supuestos (al principio la BIOS no sabe
si dispone de ellos) puertos, para determinar cuales estn presentes. El orden
en que ocurre esto depende de qu puerto se convierte en LPT1, LPT2 y en LPT3.
Primero, la BIOS se fija en la zona de direcciones 3BCh - 3BFh. Es una
parte de una zona de direcciones mayor, que va desde 3B0h hasta 3BFh y que
est reservada para una tarjeta de vdeo monocroma (MDA) o una tarjeta grfica
Hercules. Ya que hasta bien entrados los aos ochenta la mayora de PCs se
suministraba con este tipo de tarjetas, que aparte de la lgica
correspondiente al vdeo albergaban un puerto paralelo.
1
- Basic Input Output System - memoria ROM del PC que comprueba el HARDWARE e inicializa el sistema
antes de arrancar el sistema operativo.
Anexo5-7
Anexo-6 Programacin en C
Si se descubre una de estas tarjetas con su puerto paralelo, el puerto
es direccionado por la BIOS como LPT1. La siguiente tarjeta aparece
automticamente como LPT2. Si la bsqueda de una tarjeta grfica monocroma con
puerto paralelo fracasa, el siguiente puerto paralelo descubierto ser
direccionado con LPT1.
Las dos zonas de direcciones que hay aparte de la tarjeta de pantalla
estn reservadas para puertos paralelos independientes.
Todos los puertos paralelos muestran un conjunto de registros unificados
(cumple la misma misin en todos los puertos) denominado "Register-Interface"
que se compone de tres Ports (puertos o posiciones de memoria destinados a
I/O). Ocupan las tres primeras direcciones de Port de la tarjeta, por ejemplo:
378h, 379h y 37Ah en el caso de primer puerto paralelo.
Las siguientes ilustraciones presentan el significado de los diferentes
bits en los registros de puertos individuales. Comparando la denominacin de
los bits con la estructura del cable Centronics podr determinarse que estos
elementos estn en conexin directa con las lneas de conexin de un cable que
cumpla la norma Centronics. Si se escribe el valor "1" en uno de estos bits
del registro, inmediatamente se pone bajo tensin la lnea correspondiente.
Y al revs, la lnea pasa a "0", cuando el bit que la controla se pone a cero.
La norma bsica es que la lnea mantiene su estado hasta que el bit
correspondiente se modifica por software.
Debe prestarse especial atencin a las lneas con lgica negativa. Estas
son las lneas cuyo nombre est negado (llevan un guin sobre l). La
condicin que est unida a una de estas lneas aparece siempre que la lnea
est a cero (son "activas" a nivel bajo). La lnea de ERROR, P.e., muestra un
error en la salida de impresin. Pero solamente cuando este bit contiene el
valor cero. Mientras que la lnea se mantiene en "1", no existe ningn error.
LNEAS DE DATOS
Los ocho bits del primer registro de un puerto paralelo, sin embargo,
carecen de lgica negativa, Acoge ocho bits de datos, que corresponden a las
lneas de datos D0 a D7 y que por ello han de ser transferidos al perifrico.
Debe tenerse en cuenta que este registro del puerto Centronics
inicialmente est concebido como registro de salida. Esto crea una serie de
dificultades al desarrollar un programa de comunicacin entre ordenadores, ya
que se depende de la emisin y recepcin de datos, pudiendo superarse
utilizando un cable especial denominado Null-Modem (ver apartado Null-Modem).
ESTADO DE LA IMPRESORA
El estado actual de la impresora se obtiene del segundo registro, que
slo se puede leer pero no escribir. Ya que en l se reflejan las diferentes
lneas de estado de la impresora. En la siguiente ilustracin se nombran por
ello los pins por el lado del PC, que corresponden con los diferentes bits.
Un error de Time-Out, cmo lo sealiza el bit 0 del byte de estado,
aparece siempre que la BIOS intenta, durante un cierto perodo de tiempo,
transmitir datos a la impresora, pero esta no acepta los datos, o avisa que
est ocupada (BUSY, el bit 7 estar a cero en ese caso).
CONTROL DE LA IMPRESORA
El tercer registro sirve para el control de la impresora y el hardware.
Juega un papel importante durante la transmisin de caracteres. Excepto el bit
4, todos los dems bits estn unidos a las diferentes lneas del puerto.
Adems, este registro contiene un bit, con cuya ayuda se puede provocar
una interrupcin de HARDWARE, en cuanto la seal de ACK pasa a estado bajo y
con ello confirma la recepcin del ltimo carcter.
Anexo5-8
Anexo-6 Programacin en C
Contrariamente a los puertos serie, la posibilidad de utilizar
interrupciones es muy rara, ya que los puertos paralelos se hacen funcionar
con el mtodo de "Polling" (consultas sucesivas) y no el de interrupciones.
La causa principal es que la velocidad, con ms de 10.000 caracteres por
segundo, es demasiado alta como para que la impresora (o cualquier perifrico
lento) pueda seguirla.
CABLE NULL-MODEM
Si se quiere utilizar un puerto paralelo para la transferencia de datos
entre dos ordenadores, no se llegar a la meta deseada con un cable tipo
Centronics.
En los dos lados tenemos conexiones de PC, y que adems ambas son
hembras. Pero este no es el nico inconveniente, ya que la transmisin de
datos, segn el esquema tradicional slo es posible en un sentido, puesto que
en los dos ordenadores las lneas de datos estn unidas directamente, el
emisor no puede recibir datos de su contrario en las lneas de datos D0 hasta
D7 y tampoco puede enviar datos por estas lneas porque ocasionara conflicto
con la informacin enviada por el otro PC.
En la prctica se necesita, para la transferencia de datos, una conexin
bidireccional (salidas de un PC unidas con entradas del otro y viceversa),
Anexo5-9
Anexo-6 Programacin en C
para que el receptor por ejemplo pueda hacer su suma de control (Checksum) con
los datos recibidos, y enviarla al emisor, pudindose de esta manera,
determinar si los datos han sido enviados con correccin.
En este problema nos ayudan las diferentes lneas de estado, con las que
el puerto paralelo slo recibe informaciones de estado de la impresora. En
concreto son las lneas:
Todas ellas se encuentran en el segundo registro del puerto, y desde
all se pueden leer sin problemas.
Estas lneas se unirn con las lneas D0 hasta D4, para que las salidas
del emisor puedan ser ledas mediante las lneas de estado correspondientes
del receptor. Por el otro lado, Las lneas de datos D0 hasta D4 del receptor
se conectan con las lneas de estado del emisor, para que tambin sea posible
una comunicacin en este sentido.
Anexo5-10
Anexo-6 Programacin en C
Ilustr. 9 Conexiones de un cable Null-Modem paralelo
En la prctica se conectan simplemente las lneas de datos D0 hasta D4
con las lneas de estado , , , y de forma cruzada. Para
receptor como emisor es vlido por consiguiente: Quien escriba datos en los
cinco bits bajos del primer registro del puerto paralelo, los obtendr de esta
forma automticamente en los bits 3 hasta 7 del segundo registro del receptor.
Anexo5-11
Anexo-6 Programacin en C
La ilustracin 9 muestra qu pines se han de conectar en los dos
conectores de un cable Null-Modem paralelo de este estilo. Por desgracia estos
cables apenas se pueden conseguir en las tiendas,y por ello aqu esta pequea
explicacin sobre su fabricacin:
Como material se necesitan dos conectores DB 25 macho, as como un cable
apantallado de 10 o ms hilos. Sin embargo no debera ser ms largo de unos
3 metros porque a partir de esta longitud, tambin en el caso Centronics, se
debe contar con errores de transmisin.
Tal y como aparece en la siguiente tabla, primero se han de unir los
terminales 2 hasta 6 en un lado del conector con los pines 15 hasta 10 (solo
queda libre el 14) del otro lado. As que debe soldarse 5 hilos cualesquiera
del cable a los pines 2 hasta 6 del primer conector. En el otro lado del
conector soldar ahora estos hilos a los terminales 15 hasta 10, donde ha de
tenerse en cuenta la correcta colocacin - segn la tabla -.
Todo el procedimiento se ha de repetir ahora en el otro lado del cable,
para obtener un cruzamiento de los hilos. No olvidar soldar la malla a la
conexin de masa del conector.
Pin Pin Pin Pin
2 15 15 2
3 13 13 3
4 12 12 4
5 10 10 5
6 11 11 6
El cable construido tambin podr emplearse para hacer funcionar
programas de transmisin de datos comerciales, por ejemplo para trabajar con
el conocido LapLink o la utilidad de MsDOS Interlnk (e Intersvr). Ya que este
tipo de programas trabajan habitualmente con cables que no presentan otra
estructura que el cable Null-Modem aqu mostrado.
A la hora de transmitir datos, el Null-Modem permite transferir cinco
bits a travs de las lneas de datos D0 a D4 al otro ordenador. Y esto incluso
simultneamente en ambas direcciones, ya que las lneas estn cruzadas. Pero
sin un protocolo de transmisiones, todo esto no sirve de nada (para 5 o para
8 bits). De nuevo aqu se necesita una especie de lnea de "strobe", que
indique el pulso de transferencia. Se ha de sacrificar uno de los 5 bits.
DISEO PRCTICO DE E/S POR EL PUERTO PARALELO
A continuacin se presenta una sencilla aplicacin demostrativa de la
posibilidad que existe de configurar (utilizando la lneas de control como
entradas) el puerto paralelo bidireccional.
Partiendo del circuito de proteccin planteado en la pgina 5, se ha
incluido un HARDWARE adicional que permite unas condiciones favorables para
la introduccin y obtencin de informacin a travs del puerto. A continuacin
se hace una breve descripcin del mismo:
Para la ENTRADA DE DATOS se incorpora el codificador 74LS148 que
convierte una seal decimal (de 0 a 7) a su equivalente en binario. Hay
aadida una lnea que indica en todo momento si existe un dato activo aplicado
a la entrada (8 microinterruptores) obtenida del terminal GS (group select)
que aporta este integrado.
Estos cuatro bits (datos + gs) son llevados al puerto a travs del latch
Anexo5-12
Anexo-6 Programacin en C
74LS373 cuando las seales de control strobe y selectI/O lo habiliten para
ello.
En el caso de la SALIDA DE DATOS se plante en un principio el llevar
la salida del registro de almacenamiento al visualizador sin ningn elemento
interpuesto, pero se rechaz esta posibilidad por criterios de seguridad, es
decir, se incorporan unas puertas inversoras para atacar a los elementos de
salida con la idea de que sean ellas las que se encarguen de controlar los
consumos producidos por la carga y de este modo el latch de salida realice
funciones de mero transmisor de la informacin que llega del puerto y adems
conseguir un aislamiento total entre PC y dispositivo de salida.
Para finalizar destacar las dos seales de control que habilitan o
inhiben los dispositivos conectados al puerto:
- La lnea de strobe tiene como misin desconectar (virtualmente) la
proteccin de entrada. Para ello es llevada a la entrada OC del latch,
obtenindose en la salida del dispositivo un estado de alta impedancia
cuando dicha seal se encuentra activada.
- La lnea de select I/O permite actualizar el estado de los latch o
bien congelar su contenido en un instante determinado.
Para finalizar se acompaa un pequeo programa, codificado en lenguaje
C, que demuestra el funcionamiento del circuito indicado. Su labor consiste
nicamente en recoger los datos presentes en la entrada del puerto paralelo
y escribir el valor decimal de los mismos por pantalla y por la salida del
visualizador del puerto.
Anexo5-13
Anexo-6 Programacin en C
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include <graphics.h>
unsigned char dato, no_dato ,dato_ant ;
unsigned char reg_control ;
void ini_puerto_p(void) ; /* prototipos de funcin */
unsigned char toma_dato_p(void) ;
void saca_dato_p(unsigned char dato) ;
main()
{
int basura = 0 ;
ini_puerto_p() ; /* configura el estado del puerto paralelo */
clrscr() ;
printf("Programa bsico de control de E/S a travs del puerto paralelo\n\n");
printf("\t1 Conectar al puerto el mdulo de aplicacin M_1") ;
printf("\n\t2 tras pulsar una tecla, el valor presente en los\n\t");
printf(" microinterruptores se visualiza en la pantalla y leds") ;
printf("\n\n El programa permanece en este estado hasta pulsar una tecla") ;
printf("\n\n pulse ...") ;
basura = getche() ; /* limpia el buffer o memoria intermedia */
dato_ant = x ; /* toma un valor diferente a cero */
do
{
dato = toma_dato_p() ;
delay(100) ;
gotoxy(5, 15) ; /* ir a columna y fila */
if(no_dato) /* dato activo? */
{
dato++ ; /* 0 -> bit 1, 1 -> bit 2 ... 7 -> bit 8 */
if (dato_ant != dato)
{
printf("valor de entrada es = %d", dato) ;
saca_dato_p(dato) ;
}
}
else
{
if (dato_ant != dato)
{
printf(" no hay dato en entrada ") ;
saca_dato_p(0) ;
}
}
dato_ant = dato ;
}
while(!kbhit()) ; /* repite hasta pulsar una tecla */
clrscr() ;
}
void ini_puerto_p(void)
{
reg_control = inportb(0x3FA) ; /* El valor del reg. de control */
reg_control = reg_control & 0xF6 ; /* Pone a 0 B0 = strobe y B3 = select I/O */
outportb(0x37A, reg_control) ;
}
unsigned char toma_dato_p(void)
{
reg_control = inport(0x37A) ;
reg_control = reg_control | 0x09 ; /* habilita el puerto paralelo */
outportb(0x37A, reg_control) ;
dato = inportb(0x379) ; /* toma dato del puerto paralelo */
no_dato = dato & 0x80 ; /* introducido dato? */
dato = dato & 0x7F ; /* elimina el bit de mayor peso
(seala que una entrada est activa) */
dato = dato >> 4 ; /* toma aquellos bits que tienen la cifra
codificada */
return dato ;
}
void saca_dato_p(unsigned char dato_s)
{
int salida[]= { 0, 1, 2, 4, 8, 16, 32, 65, 128} ;
outportb(0x378, salida[dato_s]) ; /* en decimal */
}
Anexo5-14
A
n
e
x
o
-
6
P
r
o
g
r
a
m
a
c
i

n
e
n
C
A
n
e
x
o
5
-
1
5