Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Lpez
Snchez
http://elmartin.org korreomartin@gmail.com
sta obra est bajo una licencia CopyLeft de Creative Commons (2.5)
ndice de contenidos:
1
Cmo furula C.
Variables.
Operadores.
Funciones de C.
Funciones secundarias.
Punteros.
Cadenas.
Estructuras.
10
11
Algoritmos establecidos.
12
Ms sobre C.
13
14
Grficos.
Nota del autor: estos apuntes son solo una ayuda a la hora de
programar, no siguen un orden didctico del todo. Eso si,
ejemplos para entender las cosas sobran.
Todos los programas los he probado con Borland C++ v.
4.02, por lo que a veces me remito a algunas utilidades del
mismo para facilitar el trabajo.
Escrito entre 2001 y 2002, durante mi productivo
mdulo de Desarrollo de Aplicaciones Informticas.
Cmo furula C
rase una vez un jovenzuelo de 18 aicos que decidi meterse en el acojonante mundo de
la programacin. Y para programar necesitaba elegir uno de del montn de lenguajes que haba en
el mercado (de la piratera...).
Mucha gente le deca que el mejor y con ms futuro era el Java y que los dems lenguajes
(entre ellos C) estaban desfasados, je je... No se si C tendr futuro pero est claro que presente si
tiene (no hay ms que decir que el sistema operativo Linux est desarrollado en C en su prctica
totalidad), y nos permitir trabajar los programas hasta las mismas entraas, por lo que es un
lenguaje de bajo nivel, que no significa que tenga menos categora como piensan muchos
personajes.
Lo primero es la definicin de programa: es un conjunto de instrucciones que le damos al
cacharro ste (denominado comnmente ordenador) para que nos haga algo guapo. Para
hacernos nuestros propios programas o conjunto de instrucciones, tenemos unos leguajes que nos
echan una mano. En nuestro caso, el lenguaje se llama C. Tambin hay otro que se llama C++,
que es una ampliacin de C para poder trabajar con objetos, pero eso es otra historia y debe ser
contada en otra ocasin. Aun as, el joven cogi y se pill el programa Borland C++ v.4.02, en el
que funcionan todas las cosas hechas en C, y que contiene lo siguiente:
-Un editor de texto: que sirve para escribir lo que se llama el cdigo fuente, o puado de rdenes
escritas que formarn parte del programa
-Un compilador: que convierte nuestro cdigo fuente en un programa ejecutable (miprograma.exe).
El cacharro trabaja con 0 y 1. Si escribiramos un programa en el lenguaje del ordenador nos
volveramos locos. Para eso estn lenguajes como el C. Nos permiten escribir un programa de
manera que sea fcil entenderlo por una persona y luego es el compilador el que se encarga de
convertirlo al complicado idioma de un ordenador.
Lo primero es saber qu es lo que quieres que haga el programa. Una vez que ya lo sabes,
escribes las rdenes en lenguaje de C (que luego te lo explico por encima), y se guarda como *.c o
cdigo fuente. En el caso de mi Borland C++ ser un *.cpp, o C plus plus.
Le das al botn del rayo (caso de Borland C++ v.4.02) y lo que hace es compilarlo para ver
los fallos y pasarlo a formato objeto, *.obj. Automticamente tambin lo pasa por un linkador, que lo
convierte a lenguaje mquina, osea, a binario, para que el cacharro lo entienda. Ahora ya tenemos
un programa ejecutable, *.exe, que nos har alguna cosa guapa.
#include <stdio.h>
/* Esto imprime un mensaje tonto en la pantalla
del cacharro */
main( )
{
Luego ponemos main ( ), osea, funcin principal, que es por donde empieza todo programa
hecho en C. La forma correcta de empezar sera poner void main( ) (que en andaluz significa
funcin principal nula), ya que C se basa en el uso de funciones (se parece algo a las
matemticas) y se supone que de main, como funcin que es, debe salir un valor al final del
programa, pero tu no quieres nada de valores, sino poner esa chorrada de frase. Poniendo void lo
que hacemos es evitar que luego nos de un Warning! cuando lo compilemos.
Ahora vamos a incluir en el programa las funciones que queremos que realice, abriendo un
espacio de funciones con una { y en nuestro caso, la funcin es imprimir, es decir, printf, y lo que
queremos que imprima lo ponemos entre parntesis y entre comillas para que se entere.
Hay que tener en cuenta que estos lenguajes son un poco chuminicas y hay que poner
cantidad de cosas sin las cuales o bien nos da error al compilarlo, linkarlo o ejecutarlo, o bien nos
sale otra cosa distinta a la que buscamos.
Como hemos llamado a una funcin que ya est hecha por C, debemos colocar arriba el
archivo o librera en que lo tiene guardado, que se llama <stdio.h>, y como queremos incluirla para
que el lenguaje sepa qu hacer, la incluimos con #include <stdio.h>, arriba de todo el programa
siempre.
La carpeta de estas cosas del programa se llama include, y en ella estn guardadas un
montn de estas libreras que contienen un buen taco de funciones para alegrarnos la vida a la
hora de programar.
Despus de cada instruccin siempre hay que poner un ; para que pase a la siguiente,
pero ya no hay ms, por lo que ya cerramos con una }. Cuando le demos al botn del rayo (que es
de Borland C), osea, a construirlo todo, nos dir que o bien tenemos fallos y ms o menos nos dir
donde estn, o bien nos dir que est perfecto y lo ejecutaremos.
Para ver informacin de las funciones tenemos un montn de cosas en el Help, y si
buscamos printf nos pondr que sintcticamente debemos ponerle lo del <stdio.h>, adems de
explicar (en ingls) un puado de cosas sobre esa orden.
A veces tenemos que hacernos nosotros unas funciones que no estn definidas en las
libreras, pero eso es otra historia, y debe ser contada en otra ocasin.
Espero que como introduccin, sirva para que menos gente decida dedicarse a la
hostelera o a la construccin al intentar aprender a programar. A m poco me falt.
Variables
-int :
-short :
-long :
-unsigned :
-unsigned short :
-unsigned long :
-float :
-double :
-long double:
-enum:
enumerado. De 0 a 65.535.
Pongamos un ejemplo:
Declaramos el carcter char,
llamado un_caracter (identificador), por lo
que en RAM se guarda un byte en una
direccin, y as con int (dos bytes) y con
float (cuatro bytes).
#include <stdio.h>
main( )
{
char un_caracter;
int un_entero;
float un_real;
un_caracter = 'a';
un_entero = 15;
un_real = 27.62;
Inicializacin de variables: Esto se hace para darles un valor inicial. Las variables del mismo tipo
pueden definirse mediante una definicin mltiple separndolas mediante una coma, como por
ejemplo:
int multiplicador, multiplicando, resultado;
En la primer sentencia se
definen
e
inicializan
ambas
variables a la vez. Esta es una de
las particularidades del lenguaje C:
en los parmetros pasados a las
funciones
pueden
ponerse
operaciones, incluso llamadas a
otras funciones. Mejor te miras las
funciones.
Las variables pueden ser de dos tipos segn el lugar en que las declaremos: globales o
locales. La variable global se declara antes del main( ). Puede ser utilizada en cualquier parte del
programa y se destruye al finalizar ste. La variable local se declara despus del main( ), en la
funcin en que vaya a ser utilizada. Slo existe dentro de la funcin en que se declara y se
destruye cuando sta acaba (y realmente se crean cuando son llamadas, no al declararlas).
Si dos variables, una global y una local, tienen el mismo nombre, la local prevalecer sobre
la global dentro de la funcin en que ha sido declarada. Dos variables locales pueden tener el
mismo nombre siempre que estn declaradas en funciones diferentes.
El identificador (nombre de la variable) no puede ser una palabra clave (que use C) y los
caracteres que podemos utilizar son las letras: a-z y A-Z (ojo! la y la no estn permitidas ms
que en los letreros), los nmeros: 0-9 y el smbolo de subrayado _ (no vale el espacio).
Adems hay que tener en cuenta que el primer carcter no puede ser un nmero. Y que E
no es igual que e (sensible a maysculas y minsculas). Ejemplo:
/* Declaracin de variables */
#include <stdio.h>
int a;
main( )
/* Muestra dos valores */
{
int b = 4;
printf("b es local y vale %d",b);
a = 5;
printf("\na es global y vale %d",a);
}
Conversin de tipos: imagina por un momento que tenemos una variable llamada valor declarado
como tipo int, y queremos que se transforme en un tipo float en alguna parte del programa.
Por ejemplo queremos que en una printf aparezca una variable resultado, que es igual a
valor, pero de tipo float, pues ponemos encima de la printf lo siguiente: resultado = (float)valor; y ya
est.
Ojo con los rangos de las variables, que si pasas de uno superior a uno inferior, puedes
perder datos, ya que uno inferior tiene menos capacidad. Empezando por el superior, los rangos
estn ordenados: long double > double > float > unsigned long > long > unsigned int > int > char.
Cast: hacer un casting es obligar a que un resultado sea de un tipo en concreto. Para ello ponemos
entre parntesis delante de lo que queremos convertir, el tipo.
k = (int) 1.7 + (int) masa;
El casting se aplica con frecuencia a los valores de retorno de las funciones (tema 5). Otro
ejemplo es cuando hacemos uso de malloc( ) (tema 7), la cual debe devolvernos una direccin,
que deber ser recogida por un puntero. Para asegurarnos de que esa direccin sea la correcta,
haramos por ejemplo:
Declaramos un carcter y un puntero que
apunta a char. Luego decimos que en puntero se
guarde una direccin en la cual quepa el tamao de
letra, no sin antes exigir en el parntesis delante de
malloc( ) que la direccin devuelta sea un puntero que
apunte a char (redundante pero seguro).
...
char letra, *puntero;
...
puntero = (char *)malloc(sizeof(letra));
...
#include <stdio.h>
const float DOS_PI = 6.28;
main( )
{
printf("El valor de 2 multiplicado por Pi es %f\n",DOS_PI);
}
-auto: (automtica) es lo que en realidad deberan llevar todas las variables locales, pero por
comodidad, los que hicieron C decidieron que si no se pone, es como si estuviera. Puede servir
para diferenciar la variable de otras que sean globales, por ejemplo.
-volatile: (voltil) variables que pueden cambiar en cualquier momento. Es para el caso (poco
habitual) en que su valor pueda ser cambiado por algo que no sea nuestro programa principal, y
as obligamos al compilador a que lea el valor de la variable en memoria, en vez de mirarlo en
algn registro temporal en el que lo pudiera haber guardado para mayor velocidad.
-static: (esttica) para variables locales que
duran todo el programa. As cuando salgas de
una funcin, el valor que tena una variable no
se pierde y sigue estando tal cual si vuelves a
entrar en la funcin. Adems, en la
declaracin static int numero; la variable
numero se inicializa con el valor 0.
Antes de entender el ejemplo, hay
que mirarse el tema de cmo expresar tus
funciones.
Si en el ejemplo no pusiramos la
variable en modo esttico, su valor inicial
sera cualquier nmero entero (6345),
numero++ lo aumentara en 1 (6346), y el
programa imprimira tres veces Llamada n
6346.
#include <stdio.h>
void segunda_funcion(void)
{
static int numero;
numero++;
printf("Llamada n %d.\n",numero);
}
main( )
{
segunda_funcion( );
segunda_funcion( );
segunda_funcion( );
}
-register: (registro) pide que la variable se guarde en un sitio de rpido acceso, normalmente en los
registros del cacharro, pero si no quedan, entonces lo guarda en la RAM con las dems. As un
programa que usa estas variables va ms rpido. Sintcticamente se declaran: register tipo
variable;, como por ejemplo, register int contador.
-extern: (externa) estn definidas fuera de las funciones y se encuentran potencialmente
disponibles a todo el programa (globales). Por defecto son inicializadas a cero. Esto es avanzado...
Una variable extern es definida o creada (una
variable se crea en el momento en el que se le
reserva memoria y se le asigna un valor) una sola
vez, pero puede ser declarada (es decir, reconocida
para poder ser utilizada) varias veces, con objeto de
hacerla accesible desde diversas funciones o
ficheros.
// j, k son visibles
main( )
{
int i=3;
int func1(int, int);
...
}
Redefinir los tipos de variables: se hace usando typedef, y consiste en cambiar la palabra con la
que decimos el tipo de variable. Por ejemplo, como se crean dos nuevos tipos real y letra. Estos
nuevos tipos pueden ser usados de igual forma como los tipos predefinidos de C.
typedef real float;
luego
luego
letra mi_caracter;
Operadores
Suma
Resta
Multiplicacin
Divisin (si se quieren obtener decimales, los nmeros divididos
deben ser reales, o al menos uno de ellos).
Mdulo (resto), es decir, que si divides 5 entre 2 da 1.
++
--
+
*
/
int x = 3;
printf(%d, x)
printf(x = %d, x++);
printf(x = %d, ++x);
Operacin
Con Bits
&
0&0=0
0&1=0
1&0=0
1&1=1
0|0=0
0|1=1
1|0=1
1|1=1
<<
>>
0
0
1
1
^0=0
^1=1
^0=1
^1=0
11010011
^ 10001110
01011101
char x
Ejecucin
Valor de x
x = 7;
00000111
x << 1; 0 0 0 0 1 1 1 0
14
x << 3; 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 resultado que devuelven estos operadores es 1 para Verdadero y 0 para Falso. Si hay
ms de un operador se evalan de izquierda a derecha. Adems los operadores = = y !=
estn por debajo del resto en cuanto al orden de precedencia.
El operador ! (NOT) invierte el sentido lgico de las operaciones , as ser
!( a > b )
!( a == b )
equivale a
equivale a
(a<b)
( a != b )
Ejemplo: si ponemos printf("Que es esto => %d",(5 > 3)); luego pondr Que es esto => 1.
Operadores Lgicos: Si queremos por ejemplo poner en la condicin de una if que se cumplan
ciertas cosas y que otras no se cumplan, utilizamos estos operadores. Como las
condiciones se transforman en 1 o 0 segn se cumplan o no, estos operadores son muy
tiles en estos casos. Son:
Operador
Significado
Ejemplo
&&
||
AND
OR
.
numero entre 1 y 10
numero igual a 1 o 2
Orden de prioridad de los operadores: A la hora de ejecutar cosas con operadores, el orden que
sigue C es el de la pgina siguiente:
Operadores
Nombre
!
* /
+ < > <= >=
== !=
&&
||
Negacin (NOT)
Multiplicacin y Divisin.
Suma y Resta.
Menor, mayor, menor o igual, mayor o igual.
Igual, distinto.
AND
OR
Ejemplo:
Funciones de C
Este lenguaje tan apaao contiene un montn de funciones guardadas para que no
tengamos que especificarlas. Con solo llamarlas (ponerlas donde vayan) y poner las respectivas
includes o defines, nos harn alguna cosilla en nuestro programa. Recuerda que todas las
palabrejas van en minscula. Los ejemplos van dentro del contexto de un programa fcil. Todo
esto viene mejor en el HELP, aunque en ingls.
#include: permite ampliar el lenguaje base llamando a un fichero de C que contenga una funcin
tambin de C que vayamos a usar en nuestro programa.
-Ejemplo: el fichero de cabecera llamado "stdio.h" (standard i/o, entrada y salida estndar),
es el que contiene guardado cmo funciona la orden "printf".
#define: sirve para definir un valor fijo, como pi. Se pone arriba del todo, como las includes, para
que si luego ponemos una funcin (area=pi*radio*radio), sepa qu es pi.
-Ejemplo: #define pi 3.14159
-Nota: tambin nos puede servir para declarar funciones y otras cosas. Ver en Funciones
secundarias y Ms sobre C.
printf: Generalmente sirve para imprimir en pantalla, adems de frases, valores numricos. Su
estructura es printf(lo que quieras poner); Si es un solo carcter, va entre comillas
simples, a. Lo que pones dentro del parntesis entre comillas se llama cdigo de formato.
#include <stdio.h>
-Ejemplo: printf(El nmero 255 en octal es %o. , 255);
-Resultado: Esto pone el 255 expresado de forma octal. El %o es un molde o formato
donde se introduce el nmero que hay despus de las comillas. Imprimir: El nmero 255
en octal es 377.
Lista de formatos (estos son tambin los de scanf):
%d
Nmero entero con signo, en notacin decimal.
%i
Nmero entero con signo, en notacin decimal.
%u
Nmero entero sin signo, en notacin decimal.
%o
Nmero entero sin signo, en notacin octal (base 8).
%x
Nmero entero sin signo, en hexadecimal (base 16).
%X
Nmero entero sin signo, en hexadecimal, maysculas.
%f
Nmero real (coma flotante, con decimales).
%li
entero largo.
%lf
doble.
%Lf
largo doble.
%e
Nmero real en notacin cientfica.
%g
Usa el ms corto entre %e y %f.
%c
Un nico carcter.
%s
Cadena de caracteres.
%%
Signo de tanto por ciento: %.
%p
Puntero (direccin de memoria).
%n
Se debe indicar la direccin de una variable entera (como en scanf), y en
ella se guarda el nmero de caracteres impresos hasta ese momento.
Especificadores de ancho de campo: consiste en limitar el nmero que saldr en una printf,
de manera que si escribimos %5.2f, significa que el nmero saldr con un mximo de 5
dgitos, de los cuales 2 son decimales. Tambin vale %.2f, para que escriba solo dos
decimales, pero sin especificar el total de dgitos que tendr el nmero.
puts: sirve para poner un letrero en la pantalla. Se diferencia de printf en que esta funcin solo vale
para imprimir en pantalla frases, letreros, aunque es ms rpida a la hora de la ejecucin.
#include <stdio.h>
-Ejemplo: puts("\t Tienes los webs como los osos.");
scanf: sirve para leer un nmero que introduce el usuario. Si en el programa tenemos varias scanf
y vemos que al ejecutarlo se salta alguna, es porque tenemos residuos en la memoria, que
generalmente suelen ser frenos (\0).
Para evitarlo le ponemos fflush(stdin); tras cada scanf. Esta funcin significa quitar
caca del stdin, es decir, el buffer donde se guardan los datos capturados por teclado.
#include <stdio.h>
Ejemplo:
float inten;
printf("Introduce la intensidad --> ");
scanf ("%f", &inten);
-Resultado: declaramos un variable de tipo float llamada inten, luego pedimos el valor de
inten al usuario, que debe escribirlo, y gracias a la scanf capturamos el dato que escriba el
usuario, de tipo float, y lo guarda en la direccin inten gracias a que hemos puesto el &
delante. Los formatos son los mismos que usa la printf.
if: es una sentencia condicional. Con esta sentencia podremos utilizar los operadores relacionales
del tema de operadores. Consiste en preguntar algo para que en caso afirmativo haga
unas determinadas tareas y que en caso negativo siga adelante con el programa. Mrate
bien los operadores, que aqu se usan un montn. Con la sentencia if podemos crear dos
tipos de bifurcaciones:
-Abierta: es el mecanismo de toma de decisiones ms sencillo. Su sintaxis sera: if
(condicin) sentencia. En el ejemplo se explica una sentencia simple, en la que solo hay
una funcin (printf); pero tambin estn las sentencias compuestas, es decir, con scanf,
formulillas, etc, que necesitan estar todas contenidas dentro de {}, osea: if(numero<4)
{printf(...); numero = numero +4; etc} todo esto colocado estructuradamente, claro. Desde
estas sentencias compuestas podemos llamar a otras funciones de usuario. Se ve que
detrs de la condicin no se pone ; y esto es porque es otra funcin cuya misin se escribe
debajo, como las funciones normales.
Ejemplo:
Simple
Compuesta
int numero;
printf("Escriba un nmero: ");
scanf("%d", &numero);
if (numero>0)
printf("El nmero es positivo.\n");
sentencia encadenada
int numero;
printf("Escriba un nmero: ");
scanf("%d", &numero);
if (numero < 0)
{ printf("El nmero es negativo.\n");
printf(Fin);}
else
if (numero == 0)
{ printf("El nmero es cero.\n");
printf(Fin); }
else
{ printf("El nmero es positivo.\n");
printf(Fin); }
Nota: Cuando el programa lee una if y la condicin que impone, comprueba si se cumple o
no, de manera que si es afirmativa, coloca un 1 (normalmente, aunque basta con que sea
distinto de 0) y si es negativa un 0. Es decir si se encuentra: if (numero<=2)... y el numero
es por ejemplo 3 (mayor que dos) transformar nuestra if en: if (0)... Lo que hace es
resolver la condicin poniendo Verdadero (1) o Falso (0).
switch: esta funcin sirve para hacer una seleccin entre varias posibilidades. Es ms fcil que
andar utilizando el if...else, aunque no tan prctico. La sintaxis es algo ms complejilla.
Sera:
La expresin switch viene a decirnos
switch(algo)
interrogar
a la variable que haya entre parntesis.
{
Si
la
variable
vale una cosa (primer case), hace
case #: hace algo;
una
cosa,
si
vale
otra cosa (segundo case), hace
break;
otra,
pero
en
caso
de que no sea ninguna de los
case #: hace otra cosa;
dos
casos
planteados,
har por defecto otra cosa
break;
distinta
(default).
El
ordenador
buscar el caso que
default: hace otra distinta;
se
corresponda
con
la
realidad
y ejecutar las
}
rdenes que contenga.
Lo del break tiene su miguilla. Bsicamente lo que hace es indicar donde terminan
las funciones de un caso, para romper y poder irse a la llave que cierra el switch para
terminar. Si no los pusiramos, empezara a ejecutar todos los casos, aunque no sean los
que corresponden a la realidad. O si pusiramos el break en el segundo case, ejecutara el
primero y el segundo si la opcin correcta fuera el primer case. Ejemplo:
Con una scanf metemos una letra en opcion para que el usuario elija que se
imprima en pantalla la frmula que quiera.
Si escribe A, pondr el
switch(opcion)
rea del rectngulo, el tringulo,
el crculo y el cuadrado. Si pone
{
C, pondr la del crculo y la del
case 'A': printf("Area Rectangulo: base x altura");
cuadrado. Si pone D, solo pondr
case 'B': printf("Area Triangulo: (base x altura) / 2");
la del cuadrado. Y si pone no
case 'C': printf("Area Circulo: (2) x (PI) x (R^2)");
puedor!
entonces
escribir
case 'D': printf("Area Cuadrado: lado x lado");
Opcion
incorrecta!
break;
Recuerda
que
los
default: printf("Opcion incorrecta!");
caracteres
(char)
deben
ir
}
siempre entre comillas simples.
Si no ponemos la parte de "default", simplemente se sale del "switch" sin hacer
nada cuando la opcin no sea ninguna de las indicadas. Si para varios casos, debe hacer
lo mismo, se puede poner: case A: case B: ... case Z: printf(Letras); y ya est.
Nota: La expresin que determina qu alternativa va a activar un case de nuestro switch
puede ser una variable char o int, pero nunca, repito, nunca, de tipo float (qu cosas,
no?).
NOTA: el break sirve para salir de un switch, for, while y do while cuando convenga.
?: esto es el operador condicional y es el ltimo mecanismo disponible en C para que un programa
pueda tomar decisiones. Su sintaxis es: expresion1 ? expresion2 : expresion3. Esto se
traduce en que el ? interroga a expresion1, de manera que si es verdadera (un valor
distinto de cero), hace expresion2, pero si es falsa (0), hace expresion3.
-Ejemplo: Cuando en la ltima printf del programilla de la siguiente hoja pide la respuesta,
se va a respuesta que es
igual a preguntar si eleccion
printf(Pon un 1 o un 0);
es igual a 1. En caso
scanf(%i, &eleccion);
afirmativo escribir Uno, y en
respuesta = (eleccin = = 1) ? puts(Uno) : puts(Cero);
caso negativo escribir Cero.
printf(respuesta);
for: esta funcin permite hacer algo un nmero determinado de veces, y esto mola mazo. Su
sintaxis sera: for (valor inicial; condicin; expresin del bucle). La orden se repite desde
que una variable tiene un valor inicial hasta que alcanza otro valor final. Mejor vemos un
ejemplo:
Lo del cuadro se traduce al andaluz
diciendo lo siguiente: en la primera vuelta,
siendo x igual a 1, si x es menor o igual que 5,
for(x = 1; x <= 5; x++)
saldr del parntesis y har las instrucciones
printf(Valor de x = %d \n, x);
que haya debajo (entre llaves si son dos o
ms). Luego volver al interior del parntesis
puts(Fin del bucle.);
del for, pero esta vez ya no lee el valor inicial,
sino que vuelve a preguntar la condicin, y si
se cumple, incrementa en 1 el valor de x. Y lo har as (preguntando la condicin e
incrementando en 1 cuando se cumpla) hasta que la condicin sea falsa, con lo que saldr
del bucle y realizar la puts del final.
int x;
Dentro del espacio que hay para el valor inicial, podemos poner varios valores
iniciales para varias variables (que musical me ha quedado). Quedara: for(x=1, y=6; ... ; ...)
separado por comas y acabado en punto y coma. Esto es lo bsico, pero aun hay ms.
-Modelo 1: este uso de la for est muy curioso. Se
ve una cosilla nueva, el continue. Significa que si
la if es verdadera, y lo es cuando el resto de dividir
x por 2 es 1 (nmeros impares), y no 0 (nmeros
pares), vuelva a la for sin hacer la printf, pero si es
falsa, har la printf, y volver a la for, hasta que x
iguale o supere el valor 100. Esto pone en pantalla
todos los nmeros pares desde el 0 hasta el 98.
int x;
for(x=0; x<100; x++)
{
if(x%2) continue;
printf("%i ", x);
}
int t, count;
for(t=0; t<100; ++t)
{
count = 1;
for(;;)
{
printf("%i", count);
count++;
if(count == 10)
break;
}
}
char ch;
for(;;)
{
ch = getchar();
if(ch =='A')
break;
}
printf("Tecleo una A");
char letra;
for (letra='a'; letra<='z'; letra++)
printf("%c ", letra);
while: la palabreja inglesa significa mientras, y eso es lo que hace, que mientras se cumpla la
condicin o condiciones que impone, realizar las rdenes que lleve debajo (entre llaves
cuando sean dos o ms, como siempre). La condicin se comprueba antes de entrar en el
bucle, y si no se cumple, no entra. Suele usarse para aquellas situaciones en las que no
sabemos cuantas veces realizaremos el bucle. Se deduce que su sintaxis sera: while
(expresin) sentencia; lo cual supera a la if en cuanto a utilidades.
int x = 1;
while(x <= 5)
{
printf("Valor de x = %d\n", x);
x++;
}
printf("Fin del bucle.");
do while: Igual que la while sola, pero la condicin se comprueba despus de realizar la sentencia.
Esto significa que lo que haya debajo del do se har al menos una vez cada vez que
ejecutemos el programa. Ejemplo:
El programa dice hacer lo
const int valida = 711, clave;
que hay entre llaves, es decir, pedir la
do
clave y compararla con la valida. Si
{
no son iguales, escribir No vlida!
printf("Introduzca su clave numrica: ");
pero si es igual, saldr de las llaves y
scanf("%d", &clave);
escribir Aceptada. Este ciclo lo har
if (clave != valida) printf("No vlida!\n");
una vez siempre y lo seguir
}
haciendo mientras la clave sea
while (clave != valida);
distinta de la valida. Cada vez
printf("Aceptada.\n");
hacemos cosas ms guapas, eh?
return: sirve para devolver algn valor a la funcin en que est. Su sintaxis es return(valor);.
exit: si escribimos exit(0); en alguna parte del programa, esta funcin provoca que finalice est
donde est. Tambin cierra todos los ficheros que estn abiertos para que no se pierda
informacin alguna.
#include <stdlib.h>
getchar: significa coger un carcter para guardarlo y debemos pulsar <Intro> tras escribirlo. Si
escribes dos o ms, guardar el primero de ellos y aguarda en la posicin en la que se ha
quedado.
En el ejemplo, ir guardando en
char caracter ;
la variable las letras una a una y
sacndolas en pantalla hasta
printf("Pon algo y pulsa <Intro> para descomponerlo") ;
que coja un \n, que es el
carcter asignado al <Intro>.
while( (caracter = getchar( )) != '\n')
printf("%c\n", caracter);
#include <stdio.h>
getch: detiene la pantalla hasta que el usuario introduzca un
carcter por el teclado. Se diferencia de getchar en que
esta funcin no produce eco en la pantalla (no imprime lo
que escribes) y no hay que pulsar <Intro>.
#include <conio.h>
char car;
printf("Pulsa una tecla \n");
car = getch( );
printf("Pulsaste %c", car);
gotoxy: esta funcioncilla sirve para colocar los resultados de nuestros programas a partir del punto
que le indiquemos dentro del parntesis. El punto tendr dos componentes, la X y la Y (de
ah lo de gotoxy: ir al punto X Y indicado).
main( )
{
#include <conio.h>
gotoxy(25, 12);
puts("El gotoxy me puso aqui!");
Ejemplo: pedimos que empiece en el punto X = 25,
}
Y = 12, y a partir de ah comenzar con las
funciones, en este caso escribir un letrero.
clrscr: (clear screen) limpiar pantalla y es una orden que cuando la lee, limpia la pantalla, es decir,
que la deja en blanco, pero solo eso, que no te asustes cuando se te quede todo en blanco
por que no borra valores de variables ni nada.
#include <conio.h>
sizeof: devuelve la cantidad de bytes que ocupa una variable en la memoria RAM. Ejemplos:
char caracter;
/*Abajo devolver un 1*/
int entero;
/*Abajo devolver un 2*/
printf("El tamao de un carcter en tu sistema es %d octeto.\n", sizeof(caracter));
printf("El tamao de un entero en tu sistema es %d octetos.\n", sizeof(entero));
#include <stdio.h>
main( )
{
printf("Tamao en bytes (octetos) de:\n");
printf("char => \t %d\n", sizeof(char));
printf("unsigned char => %d\n",sizeof(unsigned char));
printf("int => \t\t %d\n", sizeof(int));
printf("short => \t% d\n", sizeof(short));
printf("long => \t %d\n", sizeof(long));
printf("unsigned int => %d\n",sizeof(unsigned int));
printf("unsigned long => %d\n",sizeof(unsigned long));
printf("float => \t %d\n", sizeof(float));
printf("double => \t %d\n", sizeof(double));
printf("long double => \t%d\n", sizeof(long double));
}
goto: siempre que un programador de C habla de esta funcin, la pone verde. Implica un salto
incondicional de un lugar a otro del programa. Esta prctica hace que los programas sean
muy difciles de corregir mantener. Si no quedara ms remedio que usarlo, (y en
programacin estructurada siempre hay remedio) debe marcarse el destino del salto
mediante un nombre seguido por dos puntos. Se puede recomendar para salir de varios
bucles anidados. Veamos un ejemplo de goto:
En este caso si c es cero se salta todas las
if( c == 0 ) goto otro_lado;
sentencias entre el if y el destino,
...
continundose con la ejecucin de lo que
otro_lado:
haya debajo de la etiqueta. El destino
...
puede ser tanto posterior como anterior al
goto que la llama.
Clasificacin de caracteres (#include <ctype.h>): sirven para comprobar el tipo de caracteres que
se reciben como entrada. Devuelven un valor distinto de 0 si el carcter es como el que pide la
funcin. Todas deben llevar detrs el argumento, por ejemplo, isalnum (variable a interrogar).
isalnum ( ): alfanumrico.
isalpha ( ): Alfabtico.
iscntrl ( ): Carcter de control (de 0 a 31 y el 127).
#include <stdio.h>
#include <ctype.h>
main( )
{
char car;
puts("Un caracter --> ");
scanf("%c", &car);
isdigit ( ): Dgito.
isxdigit ( ): Dgito hexadecimal.
islower ( ): Si va en minscula
isupper ( ): Si va en mayscula.
isgraph ( ): Carcter imprimible (menos el espacio).
isprint ( ): Carcter imprimible (incluye el espacio).
isspace ( ): Espacio.
ispunct ( ): Carcter de puntuacin.
isascii ( ): del cdigo ASCII (de 0 a 126).
if(isalnum (car) != 0)
printf("%c es alfanumerico", car);
else
printf("%c no es alfanumerico", car);
}
Conversin de caracteres ASCII a nmeros: relacionadas con esto hay las siguientes funciones.
atoi ( ): (ASCII to int) sirve para cambiar un
carcter del cdigo ASCII en un valor
numrico entero y poder usarlo como tal.
char cadena[10];
int numero;
...
while (!kbhit( )); /* Mientras no pulsemos una tecla... */
...
Funciones matemticas:
abs ( ): valor absoluto.
#include <stdlib.h>
float x = 0.5;
result = sin(x);
printf("El seno de %f es %f", x, result);
cos ( ): coseno. Igual que el seno. Su versin para enteros largos es cosl ( );.
tan ( ): tangente. Lo mismo. Devuelve el resultado de operar sen (x) / cos (x), y tambin tiene una
versin para enteros largos, tanl ( );.
exp ( ): exponencial.
float result, x = 4.0;
result = exp(x);
printf("'e' elevado a %f (e ^ %f) es igual a %f", x, x, result);
log ( ): logaritmo.
float result, x = 8.6872;
result = log(x);
printf("El logaritmo de %f es %f", x, result);
atan2 (double y, double x): Calcula el arco tangente de las dos variables x e y. Es similar a
calcular el arco tangente de y / x, excepto en que los signos de ambos argumentos son
usados para determinar el cuadrante del resultado.
ceil (double x): Redondea x hacia arriba al entero ms cercano.
sinh (double x): Regresa el seno hiperblico de x.
cosh (double x): Devuelve el coseno hiperblico de x.
tanh (double x): Devuelve la tangente hiperblica de x.
fabs (double x): Devuelve el valor absoluto del nmero en punto flotante x.
floor (double x): Redondea x hacia abajo al entero ms cercano.
fmod (double x, double y): Calcula el resto de la divisin de x entre y. El valor devuelto es x - n *
y, donde n es el cociente de x / y.
frexp (double x, int *exp): Se emplea para dividir el nmero x en una fraccin normalizada y un
exponente que se guarda en *exp.
labs (long int j): Calcula el valor absoluto de un entero largo.
ldexp (double x, int exp): Devuelve el resultado de multiplicar el nmero x por 2 elevado a exp
(inversa de frexp).
modf (double x, double *iptr): Divide el argumento x en una parte entera y una parte fraccional.
La parte entera se guarda en iptr.
Uso de la notacin E: la notacin cientfica o exponencial que se hace en matracas (por ejemplo
3*10^6, es decir, tres por diez elevado a seis) se expresa en C usando la letra E. De este
modo para expresar:
0.003
20000
3E-3
2E4
-3
3 x 10
4
2 x 10
Funciones secundarias
Adems de poder utilizar las funciones que se incluyen en las libreras de C, (como
la printf o la scanf), nosotros nos vamos a crear las nuestras. Hay cuatro formas:
1 Boton Up, es decir, haciendo funciones dentro de la funcin principal main(), de forma
que cuando por ejemplo en una printf le indicamos que en un molde ponga una funcin,
sta est definida encima de la printf.
Como puedes ver, para
#include <stdio.h>
calcular el rea del circulo, tras
obtener el radio, le decimos que el
main( )
rea vale lo que de la funcin area,
{
que est escrita encima de la printf
float radio;
del programa que la llama, en este
float area;
caso es la segunda que aparece.
printf(Calcular el rea de un crculo. Pon el radio --> );
De esta forma podemos
scanf(%f, &radio);
hacer programas que sean fciles
area = 3.14*radio*radio;
printf(El area del circulo es %.2f, area);
y no necesiten expresar funciones
}
complejas a parte.
2 Utilizar funciones secundarias: Esta es la forma que ms se utiliza. Consiste en
declarar encima del main() las funciones que vamos a llamar dentro de l (hacer
prototipos). Se pueden poner varias, y tambin podemos llamar a funciones dentro de
funciones. Cuando llamas a la funcin, C la busca debajo del main() y ejecuta sus
rdenes. Tambin puedes poner las funciones encima del main para no poner prototipos.
Vemos que en el ejemplo,
#include <stdio.h>
cuando en la segunda printf pide el
valor del rea, en la lnea de arriba
float fun_1(float valor);
ya ha metido en area un valor igual
a utilizar una funcin llamada
main( )
fun_1, en la cual vamos a utilizar el
{
float radio;
valor radio introducido por el
float area;
usuario y guardado en RAM con el
printf("Calcular el rea de un crculo. Pon el radio ");
float radio;.
scanf("%f", &radio);
La funcin tenemos que
area = fun_1(radio);
declararla arriba del main() (esto
printf("El area del circulo es %f", area);
son los prototipos), para que el
}
programa sepa el tipo de dato a
devolver y el tipo de parmetros
float fun_1(float valor)
que la funcin espera. Recuerda
{
que los prototipos deben llevar el ;.
float fun_1;
fun_1 = 3.14*valor*valor;
Cuando se va a fun_1, hay un sitio
return(fun_1);
en RAM reservado a valor en el
}
cual se guarda el valor contenido
en radio.
Lo que va delante de fun_1 es el tipo de valor que devuelve la funcin (un real), y
lo que va detrs y entre parntesis son las variables, que reciben los valores con los que
operan. Si delante de lo de fun_1 no ponemos ese float, C tomo el resultado de la funcin
como un valor entero (int). Para llamar a funciones que no necesitan pasarle valores (no
tienen parmeros), las llamaremos escribiendo por ejemplo int funcion (void), y si la
funcin no devolviese ningn valor, ponemos void funcion ().
Dentro de la funcin, declaramos un espacio para fun_1 y calculamos el area. Lo
de return(fun_1); devuelve a la funcion main() el valor del area que ha calculado la
funcion, donde pone area = fun_1(radio); de manera que en area pone lo obtenido, en la
printf pone el valor del area y se acab dar tanta vuelta.
3 Utilizando defines: el programa es muy parecido al anterior, solo que a la hora de pedir
el area para dar una respuesta, aunque nos manda a una funcin llamada fun_1, esta
est escrita como una define.
Le indica que utilice la
#include <stdio.h>
fun_1 pero con valor radio. El
#define PI 3.14159
programa se va a buscar esa
#define fun_1(valor) PI*valor*valor
fun_1 y la encuentra en las
defines. All se sustituye valor
main( )
por
radio y comienza a operar.
{
La
funcin
pide un tal PI, que se
float radio;
lo
encuentra
definido arriba
float area;
como
314159,
y
sigue
printf("Calcular el rea de un crculo. Pon el radio --> ");
operando hasta sacar lo que
scanf("%f", &radio);
area = fun_1(radio);
vale fun_1. Entonces devuelve
printf("El area del circulo es %.2f", area);
abajo el valor fun_1, que se
}
sustituye en area y que se
coloca al final en la printf que lo
pide.
Es recomendable escribir lo que vaya a ser incluido en una define en maysculas,
para que luego a la hora de analizar el programa no nos liemos con entre variables,
funciones y valores constantes.
4 Utilizando includes: Consiste en crearse uno sus propias directivas #include. Lo que en
realidad he hecho ha sido coger y guardar lo que hace y utiliza fun_1 en un archivo de
texto, y luego le ha cambiado el nombre por kuadrao.h de forma que se quede guardado
como una include. Luego esto hay que pegarlo en la carpeta include en el programa
Borland C.
Cuando en la printf pide
#include <stdio.h>
area, ve que eso es una funcin
#include <kuadrao.h>
llamada fun_1 que adems
utiliza el valor radio. Busca esta
main( )
funcin en las includes de arriba
{
y al abrir la de kuadrao.h ve que
float radio;
efectivamente contiene esto:
float area;
printf("Calcular el rea de un crculo. Pon el radio --> ");
scanf("%f", &radio);
area = fun_1(radio);
printf("El area del circulo es %.2f", area);
}
#define PI 3.14159
#define fun_1(valor) PI*valor*valor
Realiza
pues
las
operaciones
indicadas
y
devuelve el resultado de fun_1.
El resultado de fun_1 se vuelca sobre area y sta en la printf que la reclam al
principio. Si queremos meter la include en el mismo directorio en el que est nuestro
programa, en la include ponemos kuadrao.h, entre comillas.
Vamos a poner otro ejemplo: esta vez se trata de calcular el numero de bits
necesario para almacenar una matriz de N x M elementos. Hacer el calculo para
elementos que ocupen un bit, un byte y dos bytes.
1 Boton Up.
#include <stdio.h>
main( )
{
int filas;
int colum;
float resp1;
float resp2;
float resp3;
puts("Calcular el numero de bits necesario para almacenar");
puts("una matriz de N x M elementos. Se hace el calculo para");
puts("elementos que ocupen un bit, un byte y dos bytes.");
puts("");
printf("Escribe el numero de filas de tu matriz --> ");
scanf("%i", &filas);
printf("Escribe el numero de columnas de tu matriz --> ");
scanf("%i", &colum);
puts("");
printf("Has introducido una matriz de %i filas por %i columnas.", filas, colum);
puts("");
resp1 = (filas * colum)/8.0;
printf("Para elementos que ocupen un bit, el resultado es %.2f\n", resp1);
resp2 = filas * colum;
printf("Para elementos que ocupen un byte, el resultado es %.2f\n", resp2);
resp3 = filas * colum * 2.0;
printf("Para elementos que ocupen dos bytes, el resultado es %.2f", resp3);
}
2 funciones secundarias:
#include <stdio.h>
float bit(int m, int n);
float byte(int m, int n);
float dosbytes(int m, int n);
main( )
{
int filas, colum;
float resp1, resp2, resp3;
puts("Calcular el numero de bits necesario para almacenar");
puts("una matriz de N x M elementos. Se hace el calculo para");
puts("elementos que ocupen un bit, un byte y dos bytes.");
puts("");
printf("Escribe el numero de filas de tu matriz --> ");
scanf("%i", &filas);
printf("Escribe el numero de columnas de tu matriz --> ");
scanf("%i", &colum);
puts("");
printf("Has introducido una matriz de %i filas por %i columnas.", filas, colum);
puts("");
resp1 = bit(filas, colum);
printf("Para elementos que ocupen un bit, el resultado es %.2f\n", resp1);
resp2 = byte(filas, colum);
printf("Para elementos que ocupen un byte, el resultado es %.2f\n", resp2);
resp3 = dosbytes(filas, colum);
printf("Para elementos que ocupen dos bytes, el resultado es %.2f", resp3);
}
float bit(int m, int n)
{
float bit;
bit = (m * n)/8.0;
return(bit);
}
float byte(int m, int n)
{
float byte;
byte = (m * n);
return(byte);
}
float dosbytes(int m, int n)
{
float dosbytes;
dosbytes = (m * n * 2.0);
return(dosbytes);
3} Utilizando defines.
#include <stdio.h>
#define m filas
#define n colum
#define bit(m, n) (m*n)/8.0
#define byte(m, n) m*n
#define dosbytes(m, n) m*n*2.0
main( )
{
int filas;
int colum;
float resp1;
float resp2;
float resp3;
puts("Calcular el numero de bits necesario para almacenar");
puts("una matriz de N x M elementos. Se hace el calculo para");
puts("elementos que ocupen un bit, un byte y dos bytes.");
puts("");
printf("Escribe el numero de filas de tu matriz --> ");
scanf("%i", &filas);
printf("Escribe el numero de columnas de tu matriz --> ");
scanf("%i", &colum);
puts("");
printf("Has introducido una matriz de %i filas por %i columnas.", filas, colum);
puts("");
resp1 = bit(filas, colum);
printf("Para elementos que ocupen un bit, el resultado es %.2f\n", resp1);
resp2 = byte(filas, colum);
printf("Para elementos que ocupen un byte, el resultado es %.2f\n", resp2);
resp3 = dosbytes(filas, colum);
printf("Para elementos que ocupen dos bytes, el resultado es %.2f", resp3);
}
Curiosidad de #define
En el ejemplo utilizamos #define para realizar
un programa entero. Con esto se demuestra
que todo lo podramos hacer usando esta
directiva. mola o no?
#include <stdio.h>
#define START main( ) {
#define END }
#define WRITE puts
START
WRITE (Esto es un programa, ke no?);
END
4 Utilizando includes.
#include <stdio.h>
#include <matriz.h>
main( )
{
int filas;
int colum;
float resp1;
float resp2;
float resp3;
puts("Calcular el numero de bits necesario para almacenar");
puts("una matriz de N x M elementos. Se hace el calculo para");
puts("elementos que ocupen un bit, un byte y dos bytes.");
puts("");
printf("Escribe el numero de filas de tu matriz --> ");
scanf("%i", &filas);
printf("Escribe el numero de columnas de tu matriz --> ");
scanf("%i", &colum);
puts("");
printf("Has introducido una matriz de %i filas por %i columnas.", filas, colum);
puts("");
resp1 = bit(filas, colum);
printf("Para elementos que ocupen un bit, el resultado es %.2f\n", resp1);
resp2 = byte(filas, colum);
printf("Para elementos que ocupen un byte, el resultado es %.2f\n", resp2);
resp3 = dosbytes(filas, colum);
printf("Para elementos que ocupen dos bytes, el resultado es %.2f", resp3);
}
#define m filas
#define n colum
#define bit(m, n) (m*n)/8.0
#define byte(m, n) m*n
#define dosbytes(m, n) m*n*2.0
Recursividad
Consiste en crear funciones secundarias que se llamen a s mismas (recursividad directa),
o unas a otras (indirecta) como los bucles, ah dando vueltas. Ejemplos de cada uno:
Directa:
#include <stdio.h>
Indirecta:
#include <stdio.h>
void Primera(void);
void Segunda(void);
Punteros
3AF4:0001
Mirando en la barra de herramientas que tenemos arriba en C++ (File, Edit, etc) pulsamos
TOOL. Vemos que se despliega una persianita. De todas las opciones pulsamos TURBO
DEBUGGER. Esta utilidad funciona solo con programas que ya se han ejecutado alguna vez, es
decir, que ya tenemos el ejecutable exe.
Si utilizamos el programa de la siguiente pgina, que est la mar de bien, podemos ver la
direccin en la que se guardarn las variables a, b y resultado.
Una vez nos aparece la ventanita azul del TURBO DEBUGGER, pulsamos DATA, lo cual
nos abre otra persianita, y pulsamos ADD WATCH. Escribimos el nombre de una variable (a), le
damos a OK, y volvemos a entrar para meter las que nos queden (b y luego resultado).
Ahora lo que tendremos que hacer es ir pulsando el botn F7 despacio mientras vemos en
un recuadro debajo del programa como C le va dando direcciones.
Habr ocasiones, como por ejemplo cuando tropiece con la scanf, en las que se saldr al
ejecutable para que introduzcas el valor que quieras capturar, y cuando pulses <Intro> continuar
vindole las tripas al programa y a la RAM.
Punteros
Lo primero de todo es la definicin: un puntero (yo lo llamara puetero, o apuntador
como dicen en Latinoamrica) es una variable especial de C (y solo de C), que ocupa cuatro bytes
en la RAM y en la que solo podemos guardar la direccin de otra variable (por ejemplo 90AB), y
que se identifica ponindole delante el carcter * .
Estas variables se declaran poniendo primero el tipo de la variable de la que se quiere
guardar la direccin. Es decir que si en nuestro puntero vamos a meter una direccin en la cual hay
un carcter, pondremos char *mi_puntero;. Esto se traduce: mi_puntero funciona como puntero por
el * y contendr la direccin de una variable de tipo char. Luego (o antes) cogemos y declaramos
char algo = A; con lo que tenemos espacio para un carcter en la RAM guardado con el nombre
algo y que se encuentra por ejemplo en la direccin 90AB.
Si ahora queremos meter en mi_puntero la direccin en la que se encuentra esa A,
deberemos poner mi_puntero = &algo, es decir, volcar en el puntero la direccin en que se
encuentra algo. Ahora en mi_puntero he guardado 90AB. Y si por ltimo digo *mi_puntero = c;
estoy pidiendo que en la direccin 90AB se guarde una c. Increble pero cierto. Vamos a hacer un
ejemplito sencillo pero completo para suavizar esto:
El
ejemplo
no
#include <stdio.h>
necesita
demasiada
explicacin. Primero
int num, *p;
se declaran num y p,
un
entero
y
un
main()
puntero. Luego nos
{
imprime el numero
printf("escribe un numero--> ");
escrito por el usuario,
scanf("%i", &num);
su direccin en la
printf("el numero es %i\n", num);
RAM, la del puntero
p = #
para guardar la de
printf("la direccion del numero es %i\n", p);
num (p = #) y por
printf("la direccion del puntero es %i\n", &p);
ltimo
imprime
el
printf("el contenido de la direccion a la que apunta el puntero es %i\n", *p);
contenido
de
la
}
direccin que hay en
puntero (el nmero).
Ah va otro ejemplo ms apaado:
#include "stdio.h"
void main (void)
{
int a [10], *puntero, x;
a[0] = 11;
a[1] = 22;
a[2] = 33;
a[3] = 44;
puntero = &a[0];
x = *puntero;
puntero++;
x = *puntero;
x = *puntero + 1;
x = *(puntero + 1);
x = *++puntero;
x = ++*puntero;
}
Te recomiendo lo siguiente: copia este programa en C++ y lo ejecutas. Vers que si le das
al rayo, no pasa nada. Adivina por qu. Ahora busca el Turbo Debugger y una vez abierto aade
con la opcin Add Watch en la persiana de Data las variables x, puntero, a[3], a[2], a[1] y a[0], en
este orden para que lo veas mejor. Ahora ve pulsando F7 poco a poco y observa los cambios que
se producen en los contenidos de esas variables, debajo del programa. Al principio se observa que
contienen solo basura.
Lo primero que nos encontramos en el programa es la declaracin de variables, en este
caso son una cadena de diez enteros llamada a, un puntero llamado puntero, identificado como tal
por el operador de direccin * y que apunta a un entero, y otro entero de nombre x.
Luego asignamos unos valores a cuatro posiciones de la cadena. A puntero le metemos la
direccin del primer eslabn de la cadena a[0], por ejemplo 1ECE. Se observa en el Turbo
Debugger que nos ensea la direccin relativa (registro base : desplazamiento).
La siguiente instruccin es importante: al poner el * delante del puntero, pide que a x se le
introduzca el contenido existente en la direccin indicada en interior del puntero. Esto quiere decir
que a x le corresponde un 11, nmero existente en la direccin 1ECE (la de a[0]).
Nos encontramos ahora con un incremento de puntero en una unidad, pero no en una
unidad como tal, si no en un elemento.
El siguiente elemento es a[1], que por ser un int se encontrar dos bytes ms adelante, por
lo que el contenido de puntero se aumenta en dos. En el Turbo Debugger vemos que realmente el
desplazamiento ha aumentado en dos unidades o un elemento int. Esto siempre ocurre cuando se
trabaja con direcciones.
Despus volvemos a cargar en x el contenido de la direccin a la que apunta nuestro
puntero, que como ha sido aumentada, ahora se trata del contenido de a[1], un 22.
Repetimos lo del prrafo anterior, sumndole al resultado (22) un 1, con lo que en x
metemos un 23.
En la que dice x = *(puntero + 1); primero realizamos el parntesis con lo que aumentamos
en un elemento el contenido de puntero, siendo sta la direccin de a[2], por lo que en x ahora se
carga un 33. No olvides que puntero sigue aun apuntado a a[1], ya que l no ha sido modificado
desde lo de puntero++.
Despus tenemos que x = *++puntero; que implica machacar puntero por puntero + 1
debido al operador de incremento ++, por lo que en x ahora se meter el contenido que hay en la
direccin de nuestro puntero, el 33 de a[2]. Ahora el puntero a cambiado para apuntar a a[2].
Por ltimo se pide que el contenido de a[2] se aumente en 1, guardando en x un 34. Y
ahora recomiendo un descansito para la sesera.
Bien, todo esto parece muy bonito, pero es peligroso. Imagina que creamos un puntero
diciendo int *puntero; y luego decimos puntero = 167;. Vete a saber la informacin que pueda
haber en la posicin de memoria 167. No se puede modificar a la ligera la informacin porque te
puedes cargar cosas importantes.
Por ejemplo, si ponemos un valor al azar que coincide con la instruccin en cdigo
mquina de formatear el disco duro, no nos har nada de gracia cuando nuestro programa llegue
hasta esa instruccin.
Funciones con punteros: vamos a ir haciendo cosas con los punteros, para ver de qu nos
sirven.
En el ejemplo, comenzamos en main declarando un
#include <stdio.h>
entero llamado valor que contiene un 2. Imprimimos su valor
y llamamos a la funcin modif2, ordenndole que enve
void modif2(int *v)
como parmetro la direccin de valor. Esa direccin es
{
guardada en un puntero llamado v declarado en la funcin.
(*v) ++;
En ella, primero se pide que lo que haya en la direccin que
printf("Ahora vale: %d\n", *v); }
contiene v, un 2, se aumente en una unidad, un 3, y luego
pide que se imprima ese 3.
main( )
{
Nos salimos de la funcin y volvemos al lugar en
int valor = 2;
que nos quedamos dentro del main donde pide que se
printf("Ahora vale %d\n", valor);
imprima el contenido de valor, y ms de uno dir que lo que
modif2( &valor );
se imprime es un 2, pero es un 3, por que su contenido fue
printf("Ahora vale %d\n", valor);}
modificado en la funcin. Usa el Turbo Debugger para verlo.
Con esto hemos conseguido modificar una variable local de main desde una funcin
secundaria, y piensa que eso antes no sabas hacerlo. Adems, con los punteros, la ejecucin de
los programas es mucho ms rpida. Tambin nos alegran el da cuando queremos que una
funcin nos devuelva ms de un valor.
Hagmonos aqu otro ejemplo prctico para
importantes son.
El programa realiza el recuento del tipo
de letras que se escriben (minsculas,
maysculas y dgitos). Declaramos tres enteros
para guardar los resultados, y los inicializamos
con el valor cero.
Llamamos a la funcin envindole las
direcciones de las tres variables a tres punteros.
En ella adems declaramos una variables de
carcter para que en el bucle vaya mirando uno
a uno los caracteres (getchar) mientras no se
tropiece con un punto, definido arriba en la
define.
Y en los condicionales if si se encuentra
ya sea con un carcter de entre los de las
minsculas, de las maysculas, o de los dgitos,
de la tabla ASCII, esto provocar que el
contenido de la direccin a la que apunta el
puntero sea aumentada en una unidad.
Como trabajamos con las direcciones, si
luego en main volvemos a usar las variables de
enteros, stas ya han sido modificadas por la
funcin secundaria.
Al final del programa imprimir estos
nmeros y se acab. Evidentemente, el
programa pasar del resto de caracteres como
espacios, comas, etc.
Acertijos:
-Siendo ptr un puntero que apunta por ejemplo a un int, son iguales las expresiones ptr y &(*ptr)?
Solucin: S, ya que ptr es la direccin del entero, y &(*ptr) pide la direccin del contenido al que
apunta ptr, es decir, la direccin de ese supuesto entero.
-Cuando una cadena (tema siguiente) se pasa a una funcin lo que en realidad se le esta pasando
es la direccin de su elemento inicial en memoria. Son equivalentes las siguientes
declaraciones de funciones?
int fun_1(char s[ ]);
*punt;
*punt [10];
(*punt) [10];
*punt (void);
char *(*punt [10] )(char * a); punt es una array de 10 punteros que apuntan a funciones. Cada
funcin recibe un carcter, y devuelve un puntero que apunta a un
carcter.
Nota final sobre punteros: Son el arma ms poderosa de C, y se usan sobre todo con funciones,
cadenas y estructuras.
Si te preguntas porque no se usaron otros smbolos en vez de & y * para direcciones y
punteros, ya que se confunden con la & lgica de bits y el producto, consulate pensando que yo
tambin me hice siempre esa pregunta. De cualquier manera es siempre obvio, en el contexto del
programa, el uso de los mismos.
Cadenas
Cadenas (Strings)
Strings: Si ponemos por ejemplo char nombre [20], acabamos de crear un string. El nombre no
podr ser mayor de 19 caracteres (bytes), ya que el vigsimo lo ocupar siempre el freno \0
llamado carcter NULL (ASCII = 0) que dice donde acaba la cadena de caracteres.
Los strings se usan para guardar palabras o
frases. Por ejemplo reservamos un espacio de 19
bytes para un nombre con la orden char nombre[20];
y luego con scanf(%s, nombre); pedimos al
usuario su nombre para guardarlo en ese espacio.
char nombre[20];
printf("Introduce tu nombre: ");
scanf("%s", nombre);
Pero esto no es cierto, por que se ve en mi dibujo que al final aparece el obligado \0. Con
la manera soba deberamos de poner char nombre[50] = {'N', 'e', 'o, \0}; pero con la chachi no
hace falta, ya que asigna el NULL automticamente. Esto de poner el freno cuando no se ocupan
todos los espacios no lo controla el compilador. Debe quedar bien claro que el \0 solo se pone
automticamente cuando inicializamos la cadena, pero si solo la declaramos y luego le vamos
dando valores, deberemos ser nosotros los que pongamos el NULL para indicar donde acaba.
Si inicializas una cadena siendo el ejemplo char cadena [ ] = Bollycao; y luego quieres
cambiar algn elemento como la ltima letra, lo haces diciendo cadena [7] = k; con lo cual ahora la
cadena contiene la palabra Bollycak.
Otro ejemplo: primero nos encontramos con
las declaraciones de una cadena para guardar un
trozo de frase y las de tres nmero que luego pienso
usar el las printf posteriores.
En ellas lo que hago es pedir los valores de
las variables de antes, de forma que por ejemplo la
primera printf imprime El valor de a es 25986.
NOTA IMPORTANTE: si eres un poco vivo, habrs visto que en el ejemplo de arriba de cmo
capturar strings con la scanf no he puesto el & delante del identificador de la cadena en la que
quiero guardarla. Eso se debe a que en C si tienes una cadena llamada por ejemplo cad , y pides
la direccin del primer elemento con & cad [0], eso es lo mismo que poner simplemente cad,
devolviendo por ejemplo la direccin 3AF0.
Por eso, en vez de poner scanf(%s, & nombre [0]); se pone scanf(%s, nombre);. Y lo
mismo pasa por ejemplo con la gets (explicada en la pgina siguiente), que si la usramos se
pondra gets(nombre);.
Se dira pues que nombre tal cual es un puntero con una direccin que est apuntando al
primer elemento de la cadena nombre.
Aunque existen diferencias entre un puntero y el denominador de un array: el primero es
una variable que puedo asignar, incrementar, etc, y en cambio el segundo es una constante, que
apunta siempre al primer elemento del array con que fue declarado, y cuyo contenido no puede ser
variado.
Cadenas multidimensionales: Los arrays de ms de una dimensin que ms tiles resultan son
los de dos y los de tres.
En el ejemplo de abajo, sera un conjunto de siete strings de hasta nueve caracteres cada
uno ms el NULL. Para dar valores iniciales se escribir por ejemplo:
char dia_de_la_semana[7][10] = {"lunes", "martes", "mircoles", "jueves", "viernes", "sbado",
"domingo"} ;
El elemento [0][0] ser la "l" de lunes, el [2][3] la "r" de mircoles, el [5][2] la "b" de sbado,
etc. Los elementos [0][5], [1][6], etc estn inicializados con el carcter NULL y dems [0][6] y [0][7],
etc, no han sido inicializados.
Ahora va un ejemplo de cadena de tres dimensiones: un programador llega y escribe en
cdigo fuente char amigos_motes [50] [2] [20]; Esto quiere decir que crea cincuenta grupos de dos
cadenas cada uno. Cada cadena tendr un mximo de diecinueve caracteres. Su utilidad sera
tener 50 espacios para guardar el nombre de un colega y su apodo en cada uno. Otro ejemplito
ms molongo:
#include <stdio.h>
En la declaracin del
programa, la cosa quedara
como en la hermosa tabla que
he hecho aqu debajo:
main( )
{
puts("Tabla de colores");
char tabla [4] [4] [20] = { { "Rojo", "verde", "Azul", "Blanco"},
{ "Violeta", "Ambar", "Marron", "Negro"},
{ "Naranja", "Rosa", "Magenta", "Amarillo"},
{ "Plata", "Oro", "Pizarra", "Rosa"} };
int fila, columna;
printf("Introduzca la fila --> ");
scanf("%i", &fila);
printf("Introduzca la columna --> ");
scanf("%i", &columna);
printf("\nEl color del area (%i , %i) de la tabla", fila, columna);
printf(" es %s.", tabla[fila - 1][columna - 1]);
}
Se podra dar una regla: si empiezas a contar los tamaos de derecha a izquierda, el
primero siempre ser el nmero de caracteres, el segundo (si hay) el nmero de strings, y el
tercero (si hay) sern los grupos de strings que haya.
Funcin gets: (get string) captura un texto del teclado (no se para al leer un espacio en blanco).
Hace lo mismo (aunque es ms rpida) que la expresin scanf(%s, argumento); enviando al
argumento un texto que el usuario escriba. Su cabecera es #include <stdio.h>
char cad [80];
puts("Teclee una frase.");
gets(cad);
puts("Ha escrito: ");
puts(cad);
char string[10];
char *str = "abcdefghi";
strcpy(string, str);
printf("%s\n", string);
strncpy ( ): copia n caracteres del origen al destino. Sintaxis: strncpy(destino, origen, n);.
strlen ( ):mide la cantidad de elementos
(longitud) de una cadena, expresada con
un entero.
El carcter NULL se excluye. Por
ejemplo, dices char cadena [] = cinco; y
luego pides strlen (cadena);. Si lo ejecutas
te dir que tiene 5 caracteres, aunque con
el NULL son 6 en realidad.
#include <stdio.h>
#include <string.h>
main( )
{
char cad[ ] = "mi cadena";
printf("La cadena tiene %d caracteres.", strlen(cad));
}
strcat ( ): concatenar (unir) dos cadenas. Al hacerlo, a la primera se le quita el \0 del final, se
copian a partir de ah los elementos de la segunda cadena y se pone al final el \0.
Es importante que la primera tenga espacio
suficiente de antemano por que si no, guardara
datos donde no debe y los resultados son
imprevisibles.
La primera cadena ahora contendra a las
dos, y la segunda no ha variado.
El ejemplo est construido con la funcin,
de manera que al ejecutarlo produce la salida por
pantalla: La programacin es divertida.
#include <stdio.h>
#include <string.h>
main( )
{
char cadena_a[30] = "La programacion";
char cadena_b[14] = " es divertida";
strcat(cadena_a, cadena_b);
printf("La 1 cadena es \"%s\".", cadena_a);
}
strncat ( ): igual que la anterior, pero en este caso hay que indicar en nmero de elementos de la
segunda cadena que vas a aadir a la primera. Es decir, si tienes las dos cadenas del ejemplo
anterior y dices strncat (cadena_a, cadena_b, 9); guarda en la primera cadena nueve caracteres
de la segunda, y escribir en pantalla la programacin es diver, aunque esto queda bastante
cursi... La segunda cadena sigue sin cambiar.
strcmp ( ): sirve para comparar dos strings elemento a elemento. Si las cadenas son iguales, se
devuelve un 0, si no lo son pero alfabticamente la primera va antes que la segunda, da un
valor positivo (mayor que 0), y si es al revs, lo da negativo (menor que 0). En la siguiente
pgina te explico un taco de cosas de esta funcin, pero ahora tengo que rellenar este espacio
que me queda.
#include <stdio.h>
#include <string.h>
main( )
{
Nota: para que ignore si son maysculas o minsculas, usa la funcin strcasecmp ( );.
strncmp ( ): Igual que la anterior con la salvedad de que compara un nmero determinado de
caracteres. Por ejemplo, si comparamos el nombre Ester con Estercolero, evidentemente
son distintas, a no ser que demos la orden strncmp (Ester, Estercolero, 5); nos dir que s
son iguales, al comparar solo los cinco primeros caracteres de las cadenas. Devuelve los
mismos valores que strcmp.
strcmpi ( ): igual que la anterior, pero esta vez no diferencia entre maysculas y minsculas.
strncmpi ( ): compara los n caracteres indicados, sin diferenciar maysculas de minsculas.
srtdup ( ): permite duplicar dos strings. Como
lo que hace es copiar la cadena en la RAM,
lo que devuelve es la direccin donde la ha
guardado, por lo que deber recogerla un
puntero, como hacemos en el ejemplo.
Esta funcin devuelve un puntero NULL
si no encuentra espacio suficiente en la
memoria para poder guardar la cadena en
cuestin.
#include <stdio.h>
#include <string.h>
main( )
{
char *puntero, string[ ] = "la strdup!";
puntero = strdup(string);
printf("%s\n", puntero);
}
strchr ( ): lo que hace es buscar un carcter en concreto dentro de un string. Dentro del parntesis
se le ponen primero la cadena en la que debe buscar y luego el carcter en cuestin. Devuelve
la direccin en la que aparece el carcter por primera vez para confirmar que est en la
cadena, o un 0 si no se encuentra. Lo ideal sera guardar esta direccin en un puntero, ya que
nos puede resultar muy til si deseamos trabajar con ella una vez encontrado en carcter que
se busca. En el ejemplo de la siguiente pgina lo hago as, y demuestro la utilidad de usar un
puntero para la direccin.
#include <string.h>
#include <stdio.h>
main( )
{
char string [30] = "qQcTwOIeuPoBipAKshLZzMCXmN";
char *puntero;
char letra;
printf("Que letra que quieres buscar? ");
scanf("%c", &letra);
puntero = strchr(string, letra);
int resultado = (puntero - string) + 1;
if (puntero)
printf("La letra %c esta el %i\n", letra, resultado);
else
printf("No la tengo.\n");
}
B
B
523F:21D6
523F:21E1
#include <string.h>
#include <stdio.h>
main()
{
char cadena[6] = "abc,d";
char *p;
p = strtok(cadena, ",");
if (p) printf("%s\n", p);
p = strtok(NULL, ",");
if (p) printf("%s\n", p);
}
Resulta conveniente a veces uniformizar los string ledos de teclado, antes de usarlos. Hay dos
funciones que nos sirven para ello:
strlwr ( ): convierte los caracteres de una cadena de la a a la z de maysculas a minsculas.
strupr ( ): convierte los caracteres de una cadena de la a a la z de minsculas a maysculas.
strstr ( ): busca una cadena dentro de otra.
Por ejemplo strstr (mi mama me mima,
mama); nos devolver la direccin de
RAM en la que empieza la subcadena
mama de la primera cadena, pero si no
est, entonces dice que 0 patatero.
En el inevitable ejemplo, el usuario
introduce un texto corto que finaliza al
pulsar <Intro> para que la scanf haga su
trabajo. En el puntero metemos la
direccin del primero de los elementos
de la cadena.
Dentro
del
bucle,
iremos
machacando la direccin del puntero por
la direccin de la primera vez que
aparece la cadena l. Si devuelve un 0
(\0 en C) quiere decir que no existe la
subcadena en toda la cadena.
Si en el puntero hay una direccin,
aumenta en 1 el contador y en un
elemento char el puntero para que
empiece a buscar cuando entre de nuevo
en el bucle despus del primer el
encontrado.
#include <stdio.h>
#include <string.h>
main( )
{ char texto[201];
char *puntero;
int contador = 0;
printf("Pon un texto (menos de 200 letras).\n\n");
scanf("%s", texto);
puntero = texto;
do
{ puntero = strstr(puntero,"el");
if(puntero != '\0')
{
contador++;
puntero++;
}
} while(puntero != '\0');
printf("La palabra 'el' aparece %d veces.\n",contador);
}
#include <stdio.h>
#include <string.h>
main()
{
char texto[80], *puntero;
printf("Pon un texto pa quitar las vocales:\n");
gets(texto);
puntero = texto;
while(puntero != '\0')
{
puntero = strpbrk(puntero,"aeiouAEIOU");
if(puntero != '\0')
*puntero = ' ' ;
}
printf("\n%s",texto);
}
strspn Calcula la longitud del segmento inicial de s1 que consta nicamente de caracteres en s2.
strcspn Regresa el nmero de caracteres al principio de s1 que no coinciden con s2.
strerror Devuelve un mensaje de error que corresponde a un nmero de error.
Conversin de Strings a nmeros enteros: (#include <stdlib.h>) stas son funciones que
transforman un carcter del cdigo ASCII o toda una cadena de ellos a un nmero entero de
cualquier tipo.
strtod ( ): (string to d) convierte un string en un
numero entero.
strtol ( ): (string to long) convierte un string en
un numero entero largo.
strtoul ( ): (string to unsigned long) convierte
un string en un numero entero largo sin
signo.
char palabra[80];
char *puntero;
double valor;
printf("Numero con decimales: ");
gets(palabra);
valor = strtod(palabra, &puntero);
printf(El string: %s\n, palabra);
printf(El numero: %lf", valor);
Las tres ltimas, permiten la conversin de nmeros ms largos, y son bastante similares
a las tres primeras. Tambin podemos convertir nmeros a cadenas. Funciones:
#include <stdlib.h>
#include <stdio.h>
main( )
{
int number = 12345;
char string[25];
itoa(number, string, 10);
printf("string = %s\n", string);
}
Punteros que apuntan a strings: Imaginando que se te ocurriera hacer la siguiente declaracin:
char *silabas [5] = {lo, la, i, lo, la};
Aqu estamos haciendo una lista de
cinco punteros, en cada uno de los cuales
est la direccin en que se encuentran cada
una de las slabas. Es decir, que en la RAM
metemos estas cinco cadenas, y en vez de
meter las slabas en una cadena (de dos
dimensiones), lo que hacemos es guardar sus
direcciones en otra cadena.
Vamos a poner ejemplos para tragar mejor esto:
Realmente, insulto lo que hace no es apuntar a toda la cadena, si no que apunta a lo que
sera &insulto[0] que es una e, pero continua leyndola hasta que se encuentra el \0 de toda
cadena de caracteres que se precie.
2 En la misma declaracin lo
#include<stdio.h>
que hacemos es guardar en
primer lugar cuatro cadenas
main()
(nombres) en desconocidas
{
direcciones de la RAM. A la
static char *lista [4] = {"Antonio", "Juan", "Pepe", "Maria"};
vez guardamos una lista de
printf("%s %s %s %s\n", lista[0], lista[1], lista[2], lista[3]);
cuatro punteros, con las
printf("%s %s %s %s", *lista, *(lista + 1), *(lista + 2), *(lista + 3));
direcciones de cada una de
}
las cadenas (nombres).
En la primera printf se piden las cuatro cadenas, una a una, de la lista. Pero lo que se
hace en la segunda es pedir el contenido de la direccin a la que apuntan los punteros. Cuando
por ejemplo pone *(lista + 2) se refiere a *lista ms dos elementos (nombres).
Cabe destacar que el tamao de las variables vara segn las declaremos con punteros o
no. Si declarsemos char saludo[ ] = Encantado; ocupara en RAM diez bytes por las nueve
letras y el freno. Si por el contrario decimos char *saludo [ ] = Encantado; ocupa doce bytes,
entre la cadena y los cuatro bytes que ocupa guardar una direccin. En el segundo caso se
guardan una cadena con el contenido E n c a n t a d o \0 y la direccin F665 por ejemplo
que apunta a la E de la cadena.
Por el contrario, si quisiramos guardar tres nombres de un tirn, y como los punteros no
nos caen todava muy bien, seguro que haramos char nombres [3] [15] = {Ana, Juan,
Hermenegildo};
Piensa que reservar catorce bytes para guardar a Hermenegildo est bien, pero para
guardar a Ana o a Juan es un desperdicio, y que quedara de la forma 1. Pero si hacemos una
declaracin tal como char *nombres [3] = {Ana, Juan, Hermenegildo}; solo gastaramos los
bytes justos para cada nombre (bueno, y doce ms para las direcciones, pero sigue mereciendo la
pena) quedando de la forma 2. Esto se puede comprobar con la funcin sizeof.
Forma 1
Forma 2
Me voy a marcar otro ejemplaco para ejercitar mi mente (y la tuya, claro). Empecemos con
la imprescindible stdio y definiendo un par de frases con sus nombres para identificarlos.
#include <stdio.h>
#define TEXTO1 "Hola, como "
#define TEXTO2 "comes kikos?"
main( )
{
char palabra[13] , *p ;
int i ;
p = TEXTO1 ;
for( i = 0; ( palabra[i] = *p++ ) != '\0'; i++ );
p = TEXTO2;
printf("%s" , palabra );
printf("%s" , p );
}
Paso de strings entre funciones: se trata de conseguir enviar por ejemplo una cadena desde el
main a una funcin secundaria.
Como se ve en el
programa este, el mecanismo
sera, una vez guardado el equipo
del
usuario,
enviarlo
como
argumento a la funcin secundaria
(en verdad se enva la direccin en
que comienza).
sta la recibe como otra
cadena que no tiene lmite de
elementos asignado, de manera
que guarda el equipo en cuestin
como
si
la
estuvisemos
inicializando directamente.
#include <stdio.h>
meterse_con_el_usuario(char cad[ ])
{
printf("Pues el %s es la peste.", cad);
}
main( )
{
char equipo[30];
printf("Escribe tu equipo de futbol favorito--> ");
scanf("%s", equipo);
meterse_con_el_usuario(equipo);
}
La funcin sprintf ( ): Su sintaxis es: int sprintf(char *destino, const char *format, ...); Est incluida
en la librera <stdio.h>.
Funciona de manera similar a
printf, pero en vez de mostrar el texto en
la pantalla lo guarda en una variable
(destino).
El valor que devuelve (int) es el
nmero de caracteres guardados en la
variable destino.
Con sprintf podemos repetir el
ejemplo de strcat de manera ms
sencilla.
Se puede aplicar a sprintf todo lo
que vala para printf.
#include <stdio.h>
#include <string.h>
main( )
{
char nombre_completo[50];
char nombre[]="Martin";
char apellido[]="Lopez";
sprintf( nombre_completo, "%s %s", nombre, apellido );
printf( "Nombre completo: %s.\n", nombre_completo );
}
Cadenas (Arrays)
Arrays: Son vectores de nmeros. Para hacer uno, se sigue haciendo como siempre, poniendo el
tipo, el identificador y el tamao. La gran diferencia con los strings (adems de su contenido) es
que aqu no aparece nunca el entraable \0.
Si quisiramos meter directamente en un array de cinco espacios (int numero[5]) cinco
valores, en vez de ir guardndolos uno a uno (numero[0] = 200; etc), los podemos meter de una
vez poniendo:
int numero[5] = {200, 150, 100, -50, 300};
Piensa que aqu no es necesario poner el tamao (int numero[] = {1, 56, 0, -900,
666};) ya que la cantidad de elementos es la de valores entre llaves, pero bueno. Tambin se
puede inicializar parcialmente un array , por ejemplo :
int numero[10] = { 1 , 1 , 1 } ;
En ste caso los tres primeros elementos del mismo valdrn 1 , y los restantes cero en el
caso que la declaracin sea global , cualquier basurilla en el caso de que sea local.
Arrays multidimensionales: Como todas las cadenas anteriores son de una dimensin se les
llama vectores. Podemos declarar cadenas de dos o ms dimensiones, para guardar matrices por
ejemplo. A quien no haya estudiado nada de vectores o matrices, esto le puede sonar a talibn.
Imagina que queremos guardar datos sobre 100 personas, y para cada persona nos interesa
almacenar 15 nmeros, todos ellos reales. Haramos:
float datos[100][15];
Aqu el dato 10 de la persona 45 estara en datos[44][9]. Estos son los arrays ms usados,
ya que son como tablas con sus filas y columnas (dos dimensiones). Para inicializar una de ellas:
static int tabla[4][5] = { {13,
{20,
{31,
{40,
};
es el valor de
es
es el valor de
es
es
se
cumplen
mat[0]
mat[0][0]
mat[1]
mat[1][0]
mat[1][1]
#include <stdio.h>
void funcion(int n[ ] );
main( )
{
int mis_numeros[ ] = {10, 20, 30};
funcion(mis_numeros);
}
void funcion(int n[ ] )
{
printf("Mi numero 1 es el %d\n",n[0] );
printf("Mi numero 2 es el %d\n",n[1] );
printf("Mi numero 3 es el %d\n",n[2] );
}
las
#include <stdio.h>
void funcion(int v[ ])
{
v[0] = 20;
v[1] = 40;
v[2] = 60;
}
main( )
{
int vector[3];
funcion(vector);
printf("En vector[0] hay: %d\n",vector[0]);
printf("En vector[1] hay: %d\n",vector[1]);
printf("En vector[2] hay: %d\n",vector[2]);
}
#include <stdio.h>
int array [7] = {1, 100, 50, 40, 5, 1, 2};
int suma_ole(int indice)
{
if(indice == 0)
return(array[indice]);
else
return(array[indice] + suma_ole(indice - 1));
}
main( )
{
printf("%d", suma_ole(6));
}
qsort( ): es una funcin para la ordenacin rpida de cualquier tipo de datos numricos, incluida en
la librera <stdlib.h>. Para que funcione necesita de cuatro parmetros, que son la direccin del
vector, el nmero de elementos del vector, el tamao de cada elemento y una funcin de
comparacin hecha por nosotros. Sintaxis:
qsort (&vector, elementos, tamao_elemento, funcin);
Se necesita especificar el tamao de cada elemento ya que qsort( ) puede ordenar
vectores de diferentes tipos, y la funcin de comparacin es invocada por qsort( ) para comparar
los distintos elementos del vector. Al comparar dos elementos, a y b, devolver un valor positivo si
a es mayor que b, negativo si es al revs o 0 si son iguales.
En el ejemplo de la pgina siguiente, se hace una conversin de tipos algo complejilla, por
lo que hay en el tema de variables un apartado que te tienes que haber mirado ya.
#include <stdio.h>
#include <stdlib.h>
void imprimir_vector(int in[ ],int k);
int cmp(const void *a, const void *b);
int comparaciones = 0;
main( )
{
int vector_ent[20] = {6, 7, 5, 8, 4, 9, 3, 0, 2};
int n = 9;
printf("Vector no ordenado => ");
imprimir_vector(vector_ent,n);
qsort(vector_ent, n, sizeof(vector_ent[0]), cmp);
printf("Vector ordenado => ");
imprimir_vector(vector_ent,n);
printf("\nNumero de comparaciones es %d",comparaciones);
}
int cmp(const void *a, const void *b)
{
comparaciones++;
return(*((int *) a) - *((int *) b));
}
void imprimir_vector(int in[ ], int k)
{
int i;
for(i = 0; i < k; i++)
printf("%4d",in[i]);
printf("\n");
}
#include <stdio.h>
main( )
{
char ch;
char nombre[20];
printf( "Escribe tu nombre: " );
scanf( "%[A-Z]s", nombre );
printf( "Lo que recogemos del scanf es: %s\n", nombre );
printf( "Lo que haba quedado en el buffer: " );
while( (ch = getchar( )) != '\n' )
printf( "%c", ch );
}
Estructuras
En C existen mecanismos para organizar los datos (variables). Vamos a empezar con una
sencilla, para calentar.
enum: significa enumerado, y lo que hace es lgicamente enumerar una serie de cosas. Su
sintaxis sera:
enum etiqueta {cosa1, cosa2, cosa3... cosa65.535};
Es decir, que creamos un molde de un conjunto llamado etiqueta, y a cada cosa le
corresponde un nmero, de forma que cosa1 es 0, cosa2 es 1, y as sucesivamente. Luego si en el
programa queremos utilizar este conjunto para algo, deberemos asignarle un nombre a esa
etiqueta:
enum etiqueta mis_cosas;
Con esto, mis_cosas es el nombre del conjunto, aunque yo tambin puedo utilizar ese
conjunto con todos los nombres que quiera.
De igual manera, cuando en el programa utilicemos por ejemplo cosa2, nos estaremos
refiriendo al elemento 1. Cuando declaramos la variable de tipo enumerado, estamos indicando
que mis_cosas solo puede tomar los valores que hay entre corchetes.
Creamos una
lista o conjunto llamada
cosas, y esas mismas
cosas las metemos en
un string. Dentro de
main generamos una
variable enumerada del
tipo cosas llamada
bocadillo.
Por medio del
for iremos sacando en
pantalla el nmero de
ingrediente (ms uno
para que no empiece a
contar por el 0) y el
ingrediente en cuestin
sacado del string.
#include <stdio.h>
enum cosas {lomo, patatas, alioli, tomate};
char ingrediente [4][10]= {"lomo", "patatas", "alioli", "tomate"};
int contador = 0;
main( )
{
enum cosas bocadillo;
printf("Bocata del I.P. por Martin Lopez Sanchez\n\nIngredientes:\n\n");
for(bocadillo = lomo; bocadillo <= tomate; bocadillo++)
{
printf("Ingrediente n %i : %s\n", bocadillo + 1, ingrediente[contador]);
contador++;
}
}
Lo cierto es que esto sirve ms para entender mejor el union y el struct que para otra cosa,
pero como dijo alguien sumamente aburrido: El saber no ocupa espacio. Donde yo ms he visto
lo de enum ha sido en programas de C++.
struct: Una nueva herramienta de C es la estructura de variables. Consiste en agrupar una serie
de variables en un molde o conjunto. Por ejemplo:
struct datos
{
char nombre[20];
int edad;
float estatura, peso;
};
Ahora martin es una variable del tipo datos. De esta manera cualquier funcin dentro del
programa puede usar esta estructura haciendo referencia a su etiqueta (datos).
Pero si lo que necesitamos es solo una estructura para
todo el programa de un tipo en particular, entonces podemos
coger y declararla asignndole directamente su variable.
Para asignar valores a las variables de la estructura,
debemos usar el nombre de la variable de estructura, seguida del
carcter punto (.), y despus la variable en concreto. Es decir:
struct
{
char nombre[20];
int edad;
float estatura, peso;
} martin;
int edad;
float peso;
} datos_personales;
main( )
{
static datos_personales persona_1 =
{
"Martin Lopez Sanchez",
19,
90.5
};
printf("Datos de la persona #1:\n\n");
printf("Nombre =>\t%s\n", persona_1. nombre);
printf("Edad => \t%d\n", persona_1.edad);
printf("Peso => \t%.1f\n", persona_1.peso);
Puntero a una estructura: igual que un puntero puede apuntar a un int, float, etc, tambin puede
apuntar a una estructura como variable que es. Aunque esto tiene una pega chica: un puntero no
nos reserva un hueco en memoria para guardar una estructura, simplemente guarda una direccin.
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
char nombre[50];
int edad;
float peso;
} datos_personales;
main( )
{
datos_personales *ptr;
ptr = malloc(sizeof(datos_personales));
printf("Nombre => ");
gets(ptr -> nombre);
printf("Edad => ");
scanf("%i",&ptr -> edad);
printf("Peso => ");
scanf("%f",&ptr -> peso);
printf("\n\n");
printf("Nombre:\t%s\n",ptr -> nombre);
printf("Peso:\t%f\n",ptr -> peso);
printf("Edad:\t%i",ptr -> edad);
}
#include <stdio.h>
typedef struct
{
char nombre[30];
char telefono[12];
} mi_estructura;
mi_estructura leer_datos(void)
{
mi_estructura reg;
printf("Nombre del colega => ");
gets(reg.nombre);
printf("Su telefono => ");
gets(reg.telefono);
return(reg);
}
void imprimir_datos(mi_estructura reg)
{
printf("Conoces a alguien que se llama %s y su
telefono es el %s.", reg.nombre, reg.telefono);
}
main( )
{
mi_estructura un_colega;
un_colega = leer_datos();
imprimir_datos(un_colega);
}
Cadenas de Estructuras: consiste en crear un tipo de variable que sea una estructura y luego crear
una cadena de ellas.
typedef struct
{
Hemos definido aqu un array de 100 elementos,
char material[50];
donde cada uno de ellos es una estructura del tipo item
int existencia ;
compuesta por tres variables: un int, un double y un string
double costo_unitario ;
de 50 caracteres.
} item ;
Los arrays de estructuras pueden inicializarse de la
manera habitual. As en una definicin de stocks, podramos
item stock[100] ;
haber escrito:
El string material[ ] es inicializado por medio de las
comillas y luego en forma ordenada se van inicializando
item stock1[100] =
cada uno de los miembros de los elementos del array
{
stock1[ ]. Explicitamos el tamao de la cadena, [100], y slo
"tornillos", 120, 1.5 ,
inicializamos los tres primeros elementos, por lo que los
"tuercas", 200, 0.9 ,
restantes quedarn cargados de basura si la definicin es
"arandelas", 90, 0.1
local de cero si es global, pero de cualquier manera estn
};
alojados en la memoria.
Si en cambio no ponemos el nmero de elementos, ser el compilador el que calcule la
cantidad de ellos, basndose en cuantos se han inicializado.
Cadenas multidimensionales de Estructuras: Si cuando tenamos una cadena de dos dimensiones
decamos que era como una tabla de elementos (con filas y columnas), ahora lo que hacemos es
una tabla de estructuras.
Primero declaramos una estructura con una
typedef struct
seria de variables (las que sean) y luego creamos una
{
cadena de dos dimensiones que sea del tipo de la
float tabla[5][6];
estructura.
int numero;
Para referirnos al numero de la estructura
char segundo_miembro_3;
segunda de la fila tercera, diramos:
} registro;
matriz [2] [1].numero;
Una Estructura dentro de otra: si tenemos en cuenta que una estructura es una variable ms,
entonces podemos meter, dentro de una estructura, otra.
typedef struct
{
int primero;
}estructura1;
typedef struct
{
estructura1 segundo;
} estructura2;
main( )
{
estructura2 valores ;
valores.segundo.primero = 54;
printf("El valor es %d", valores.segundo.primero);
}
union: es igual a las estructuras, excepto por la manera de usar la memoria. En la union todas las
variables que sean suyas ocupan la misma zona de memoria.
Comenzamos
el
programa creando una
union que podr contener
o un entero o un real (de
ah el nombre de variable
entero_o_real).
Como la variable
que ms ocupa es el float,
el tamao de memoria que
usar la unin ser de 4
bytes, como nos debe
mostrar la primera printf
que aparece.
Pedimos que use
el espacio que tiene
entero_o_real para poder
guardar en valor_entero
un entero, de manera que
sacamos por pantalla el
valor y la direccin en que
comienza.
#include <stdio.h>
main( )
{
union
{
int valor_entero;
float valor_real;
} entero_o_real;
printf("Tamao de la union => %i bytes.\n", sizeof(entero_o_real));
entero_o_real.valor_entero = 969;
printf("El valor entero es %d\n", entero_o_real.valor_entero);
printf("Direccion de comienzo => %i\n", &entero_o_real.valor_entero);
entero_o_real.valor_real = 696.96;
printf("El valor real es %f\n", entero_o_real.valor_real);
printf("Direccion de comienzo => %d\n", &entero_o_real.valor_real);
}
Luego, pedimos que en el mismo espacio (borrando antes lo que haya) meta en valor_real
un nmero real, y hacemos lo mismo. Vemos que la direccin de las dos variables de la union es la
misma. Todas las variables se almacenan en la misma zona de memoria, pero por separado. Por lo
dems, son como las estructuras.
Gestin dinmica de la
Memoria
Entendemos por asignaciones de memoria de forma dinmica aquellas que son creadas
por nuestro programa mientras se estn ejecutando. Su gestin debemos realizarla nosotros los
programadores de C.
Para empezar vamos a ver dos funciones incluidas en la librera stdlib.h que nos sirven d
introduccin en este mundillo.
malloc ( ): (memory allocation) esta funcin devuelve una direccin, por lo cual se necesitara de
un puntero para recibirla (ver tema 8).
void *puntero;
Lo que hace es buscar un
tamao de memoria en concreto para
puntero = malloc(1000);
guardar algo, de manera que si en la
RAM
encuentra
ese
espacio,
if(puntero = = NULL)
devuelve la direccin en que
printf("No hay espacio suficiente para 1000 bytes.");
empieza.
else
En el ejemplo de la derecha,
{
declaramos un puntero que contendr
printf("Se pueden guardar 1000 bytes en la");
una direccin (apunta a void nulo
printf("\ndireccion %p", puntero);
por que no va a apuntar a ninguna
}
variable) devuelta por malloc. A partir
de esa direccin, podemos contar mil
...
bytes libres para poder usarlos.
Si no encuentra ese espacio, devolver un puntero a NULL, osea, a nada. Como devuelve
un puntero que en principio apunta a void, es decir, un puntero a cualquier cosa, a la hora de
ejecutar ests funciones es aconsejable realizar una operacin cast (de conversin de tipo).
Declaramos un carcter y un puntero que
apunta a char. Luego decimos que en puntero se
guarde una direccin en la cual quepa el tamao de
letra, no sin antes exigir en el parntesis delante de
malloc( ) que la direccin devuelta sea un puntero
que apunte a char (redundante pero seguro).
...
char letra, *puntero;
...
puntero = (char *)malloc(sizeof(letra));
...
float *mat;
mat = (float *) calloc (20, sizeof(float));
if (mat = = NULL)
{
printf ("\nNo hay memoria");
exit(0);
}
...
char *str;
str = (char *) malloc(15);
strcpy (str, "Martin");
printf("Contenido: %s\nDireccion: %p\n", str, str);
str = (char *) realloc(str, 20);
printf("\nContenido: %s\nNueva direccion: %p\n", str, str);
...
free ( ): siempre que usemos una asignacin dinmica, debemos liberar la memoria asignada con
malloc o calloc usando la funcin free (puntero); Aqu puntero es el puntero devuelto que
debemos liberar, para que el cacharro pueda usar la memoria para otras cosas.
#include <stdio.h>
#include <stdlib.h>
int main( )
{
int bytes;
char *texto;
printf("Cuantos bytes quieres reservar: ");
scanf("%i",&bytes);
texto = (char *) malloc(bytes);
/* Comprobamos si ha tenido xito la operacin */
if (texto)
{
printf("Memoria reservada: %i bytes \n", bytes);
printf("El bloque comienza en la direccin: %p\n", texto);
free( texto );
/* Ahora liberamos la memoria */
}
else
printf("No se ha podido reservar memoria\n");
}
Hay que tener cuidado a la hora de liberar la memoria. Tenemos que liberar todos los
bloque que hemos asignado, con lo cual siempre debemos tener almacenados los punteros al
principio de la zona que reservamos.
Si mientras actuamos sobre los datos modificamos el valor del puntero al inicio de la
zona reservada, la funcin free probablemente no podr liberar el bloque de memoria.
Ejemplo prctico: Vamos a ver unos ejemplos para ver la utilidad de esta cosa de la asignacin
dinmica de memoria.
Supongamos que deseamos programar una serie de funciones para trabajar con matrices.
Lo primero que se me ocurre es definir una estructura de datos matriz, compuesta por una matriz y
sus dimensiones puesto que nos interesa que nuestras funciones trabajen con matrices de
cualquier tamao.
Por tanto la matriz dentro de la estructura tendr el tamao mximo de todas las matrices
con las que queramos trabajar y como tenemos almacenadas las dimensiones trabajaremos con
una matriz de cualquier tamao, pero tendremos reservada memoria para una matriz de tamao
mximo.
Estamos
desperdiciando
definicin de este tipo sera:
memoria.
Una
typedef struct
{
float mat[1000][1000];
int ancho,alto;
} MATRIZ;
struct mat
{
float *datos;
int ancho,alto;
};
typedef struct mat *MATRIZ;
de tipo MATRIZ, y
temp, que tambin
y es la matriz que
memoria de forma
main( )
{
MATRIZ a;
a = inic_matriz (3, 3);
...
borrar_matriz( a );
}
Otra definicin posible del problema podra ser as. Lo primero es el programa principal, el cual
llama a la funcin secundaria inic_matriz( ).
main( )
{
Garbage collection
Cuando declaramos una variable se reserva la memoria suficiente para contener la
informacin que debe almacenar. Esta memoria permanece asignada a la variable hasta que
termine la ejecucin del programa (funcin main). Realmente las variables locales de las funciones
se crean cuando stas son llamadas pero nosotros no tenemos control sobre esa memoria, el
compilador genera el cdigo para esta operacin automticamente.
En este sentido las variables locales estn asociadas a asignaciones de memoria
dinmicas, puesto que se crean y destruyen durante la ejecucin del programa.
As entendemos por asignaciones de memoria dinmica, aquellas que son creadas por
nuestro programa mientras se estn ejecutando y que por tanto, cuya gestin debe ser realizada
por el programador.
Hay que tener cuidado a la hora de liberar la memoria (free( )). Tenemos que liberar
todos los bloques que hemos asignado, con lo cual siempre debemos tener almacenados los
punteros al principio de la zona que reservamos. Si mientras actuamos sobre los datos
modificamos el valor del puntero al inicio de la zona reservada, la funcin free probablemente
no podr liberar el bloque de memoria.
El mecanismo es siempre el mismo: cuando se instancia un objeto se reserva un bloque de
memoria en el montn y se devuelve una referencia (o puntero) al comienzo del mismo. Cuando
este objeto deja de ser utilizado (por ejemplo, establecindolo a null) lo que se hace es destruir la
referencia al mismo, pero el objeto permanece en el espacio de memoria que estaba ocupando, y
ese espacio de memoria no se puede volver a utilizar hasta que no sea liberado.
De ah viene la necesidad de desarrollar un mecanismo que nos perita controlar toda la
basura (garbage) que vamos dejando suelta por la memoria ocupando espacio innecesario.
El Garbage Collection es el proceso por el cual el almacenaje asignado dinmicamente en
memoria es recogido durante la ejecucin de una aplicacin. El trmino usualmente se refiere al
periodo automtico de recoleccin por un garbage collector para liberar bloques especficos de
memoria (lo que se usa en C++ son los denominados destructores).
Hasta el momento hemos estado realizando una asignacin y desalojo de memoria sin
tener en cuenta el momento en el que este ltimo se lleva a cabo. Y es responsabilidad exclusiva
del programador la recuperacin de la memoria asignada a objetos que dejan de estar en uso y el
evitar que se produzcan lo que se llaman referencias colgadas, es decir referencias a posiciones
de memoria que se supone que estn libres. Este es uno de los problemas que hace que la
programacin en estos lenguajes sea tediosa y es la causa de gran parte del tiempo empleado en
la depuracin de los mismos.
Para nosotros, los que estudiamos lenguajes como C y C++, y que tenemos la conviccin
(espero) de llegar a ser los autnticos gurs de este lenguaje tendremos que aprender a gestionar
la memoria a nuestro antojo. Aunque para aquellos que empiecen con el nuevo C# van a pensar
que se les acaba de quitar un gran peso de encima, ya que este nuevo lenguaje incluye su propio
recolector de basura, por lo que ya no les ser necesario liberar la memoria dinmica.
Como hasta ahora, cuando se terminaba el programa se perdan los datos, vamos a ver
como podemos guardar informacin en el disco duro del cacharro. Para ello, existe en C una nueva
variable, FILE, que es una estructura definida en el archivo de cabecera <stdio.h>, y que nos sirve
para establecer una conexin o un flujo de informacin entre nuestro programa y el disco duro del
cacharro. La manera de trabajar es crear un puntero que apunte al comienzo del fichero.
La idea ms comn del concepto de fichero es un conjunto de posiciones de memoria en
las cuales podemos almacenar y recuperar informacin. Los ficheros en C los podemos clasificar,
segn la informacin que contengan en:
-ficheros de texto: la informacin est guardada como una secuencia de caracteres de la tabla
ASCII, organizados en lneas que acaban con el carcter de nueva lnea (\n). La forma de leerlos
es usando el mismo programa que los creo. Al guardarlos se producen ciertas modificaciones por
lo que no guardan una igualdad carcter a carcter con el dispositivo externo. Existen en la
mayora de sistemas, como Microsoft o UNIX.
Nota: 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, al revs, las nuevas lneas se traducen en
retorno de carro / alimentacin de lnea. En modo binario no se producen tales traducciones.
-ficheros binarios: secuencia de bytes (ceros y unos) que tiene una correspondencia 1 a 1, con el
dispositivo externo. Estos son aun ms complejos de descifrar, y no existen por ejemplo en
sistemas UNIX (y sucedneos), pero s en sistemas Microsoft.
Cuando se guarda un fichero los caracteres se guardan en el buffer y solo se transmiten
cuando ste se llena. Esto lo hace el sistema.
Para empezar por lo bsico, he hecho un
programa que crea un archivo de texto de extensin txt
y he escrito en el una frase.
Consiste en crear un puntero que apunta a un
archivo (FILE), y a ese puntero le asignamos, mediante
la funcin fopen (open file) una direccin de la memoria
(disco duro) en la cual ha creado un archivo llamado
tontera.txt, listo para que en el escribamos algo con el
parmetro w (write).
Ahora ya tenemos creado y abierto un archivo
de texto, listo para recibir informacin, y con la funcin
fputs (put string in file), escribimos una frase
empezando en la direccin a la que apunta el puntero.
#include <stdio.h>
main( )
{
FILE *puntero;
puntero = fopen("tonteria.txt","w");
fputs("Mi primera tonteria", puntero);
fclose(puntero);
}
Funciones de Ficheros: todas incluidas en <stdio.h>. Que no se pase lo de que cuando usamos
funciones para escribir en ficheros, estos tendrn que haber sido abiertos en modo escritura o para
aadir datos.
fopen ( ): esta funcin devuelve una direccin (que debe recibir un puntero) en la que ha creado
un archivo. Su sintaxis sera: fopen (nombre.extensin, modo de acceso); poniendo por
ejemplo C:\\tontera.txt, y en modo de acceso ponemos uno de los modos de apertura del
fichero, mediante los cuales le diremos al compilador si se trata de un fichero de texto o
binario, y si vamos a leer o escribir en el fichero. Son los siguientes:
r
r+
w
w+
a
a+
Para indicar que el archivo es binario y no de texto, detrs del modo de acceso se debe
colocar la letra b (por ejemplo: rb+). En realidad, para ficheros de texto debera ponerse
tambin una t (rt+), pero por defecto, si no se pone, se toma como tal.
Si ha existido algn problema al abrir el fichero (no existe el fichero que intentamos abrir
para lectura, no hay espacio suficiente en el disco al tratar de crear el fichero, la unidad de
disco no est preparada, que el disco est daado fsicamente, la impresora no est
conectada, etc), devolver NULL.
fclose ( ): sirve para cerrar los ficheros que hallamos usado, indicando como argumento el
puntero que lo apunta. De no usarlo, perderamos la informacin del fichero (quedaran
corruptos). Esta funcin devuelve 0 si todo ha ido bien.
freopen ( ): cierra el fichero apuntado por el puntero y reasigna este puntero a un fichero que
ser abierto. Su sintaxis es: freopen (nombre del fichero,"modo de apertura",puntero); donde
nombre del fichero es el nombre del nuevo fichero que queremos abrir, junto al modo de
apertura, y finalmente el puntero que va a ser reasignado.
fcloseall ( void ): cierra todos los ficheros que nuestro programa tenga abiertos. Devolver el
nmero de ficheros cerrados como un entero, o EOF (end of file) en caso de error o fin de
archivo, ms o menos como NULL en otras funciones.
fputc ( ): ( putc) escribe un carcter en un fichero de texto por llamada. Devuelve un entero si
la escritura es correcta o EOF en caso de error. Su sintaxis es: fputc (carcter, puntero);
En el programa, habiendo creado el
FILE *puntero;
puntero a archivo y una variable de tipo
char caracter;
carcter, pedimos al usuario que escriba
puntero = fopen("cosa.txt", "w");
una cadena de caracteres.
El
usuario
ir
introduciendo
printf("Escribeme unas palabras: ");
caracteres a la vez que se irn
escribiendo en el fichero con la putc del
while((caracter = getchar( )) != '\n')
bucle, hasta que le de por pulsar <Intro>,
caracter = putc(caracter, puntero);
de forma que sale del bucle, y ya en la
fclose(puntero);
ltima instruccin cierra el archivo.
#include <stdio.h>
main( )
{
FILE *punt;
char nombre[20] = "Martin";
char nuevo[20];
int edad = 19;
int nueva;
/* Uso de la fprintf ( ) */
punt = fopen("datos.666", "wb");
fprintf(punt,"%s %i", nombre, edad);
fclose(punt);
/* Uso de la fscanf ( ) */
fscanf ( ): Lee los argumentos del fichero. Al igual
que con un scanf, deberemos indicar la direccin
de memoria de los argumentos con el smbolo &.
fgetc ( ): ( getc) Lee un carcter de un fichero (abierto en modo lectura). Deberemos guardarlo
en una variable.
#include <stdio.h>
En el ejemplo, tras declarar una variable de tipo
carcter, pedimos al usuario que introduzca un
carcter por teclado.
La tecla que pulse se ir directamente al stdin, al
buffer del teclado, y con la funcin getc pedimos que
el carcter que haya en el buffer lo vuelque sobre
nuestra variable, de manera que luego la printf
muestre que efectivamente lo que se puls lo pudo
capturar la funcin.
FILE *puntero_a_archivo;
char caracter;
puntero_a_archivo = fopen("tonteria.txt","r");
while((caracter = getc(puntero_a_archivo)) != EOF)
printf("%c", caracter);
fclose(puntero_a_archivo);
main( )
{
char tecla;
printf("Pon un caracter: ");
tecla = getc(stdin);
printf(Has pulsado: ' %c ' , tecla);
}
Nota: Si en el primer ejemplo de fgetc el usuario pulsa por ejemplo: pongo un montn de
caracteres a ver que pasa, la getc solo capturar el primero de los caracteres que se
encuentre, la p. Por ltimo, la funcin devolver un EOF en caso de error o de llegar al final
del archivo del que coge los caracteres.
fgetw ( ): captura un numero entero de un fichero. Su sintaxis es: fgetw (puntero);
fgets ( ): lee un nmero de caracteres de un fichero almacenndolos en una cadena. Si se
encuentra el carcter de nueva lnea ser parte de la cadena, al contrario que con la funcin
gets( );. Su sintaxis sera:
fgets (string, caracteres, puntero);
Aqu, string es la cadena que recibir la informacin leda, luego va el nmero de
caracteres que se van a leer, y por ltimo el puntero que apunta al archivo.
fwrite ( ): nos sirve para poder guardar registros en archivos (variables, estructuras, cadenas,
etc). Su sintaxis, algo compleja, sera:
fwrite (®istro, tamao, nmero de registros, puntero a archivo);
Lo primero que se pone es la direccin del
registro, despus el tamao (generalmente
usando sizeof), luego la cantidad de registros
que leer cuando se la llame y por ltimo el
puntero que apunte al archivo en cuestin.
Viendo el ejemplo, tenemos una estructura
simple, con unas variables en las uqe
guardaremos informacin sobre un grupo
musical.
Dentro de main, lo de siempre: el puntero
que apunte a un archivo, en este caso
grupos.wea en el que guardaremos la
informacin. Los datos que introduzca el
usuario se guardan en la estructura
temporalmente.
#include <stdio.h>
#include <conio.h>
struct
{
char nombre[40];
char estilo[25];
int discos;
}grupo;
main( )
{
FILE *punt;
punt = fopen("grupos.wea","w");
printf("Grupo => ");
gets(grupo.nombre);
printf("Estilo => ");
gets(grupo.estilo);
printf("N de discos editados => ");
scanf("%i", &grupo.discos);
Con este programa solo guardamos informacin para un solo registro, y para guardar para
un montn, con hacer un bucle, solucionado.
struct
{
char nombre[40];
char estilo[25];
int discos;
}grupo;
FILE *punt;
printf("El fichero grupo.wea contiene:");
punt = fopen("grupos.wea","r");
fread(&grupo, sizeof(grupo), 1, punt);
printf("\n\nGrupo => %s\n", grupo.nombre);
printf("Estilo => %s\n", grupo.estilo);
printf("Discos editados => %i", grupo.discos);
fclose(punt);
ENOENT
ENOTSAM
Valor
0
1
2
Resultado
comienzo del archivo
posicin actual
fin de archivo
long int l;
FILE *f;
f = fopen(archivo.aaa, rb);
l = ftell(f);
printf ("El fdichero es de %li bytes", l);
clearerr ( ): pone a 0 el indicador de error. Una vez activado, permanecer as, haciendo que
cualquier funcin sobre este fichero devuelva EOF. La sintaxis:
clearerr (puntero);
feof ( ): Permite saber si hemos llegado al final de fichero cuando estemos leyendo, de lo
contrario podran producirse errores de lectura no deseados. Devuelve el resultado 0 si se
encuentra en el final de archivo u otro valor si se encuentra en cualquier otra parte del mismo.
feof (puntero);
En el ejemplo, he hecho un bucle que se podra traducir
como: mientras no sea el final de fichero leer...", siendo
punt el puntero que apunta al archivo en cuestin, como
siempre.
Entrada / Salida de bajo nivel: esta forma de Entrada / Salida es sin almacenamiento intermedio,
es decir, sin buffer (cuando vayamos a leer o escribir se genera un acceso al disco o dispositivo
directamente para traer poner un determinado nmero de bytes). Aqu en vez de usar punteros a
archivos, se emplea un manejador de archivo de bajo nivel o descriptor de archivo (por lo cual
debe ser recogido por un entero), el cual da un entero nico para identificar cada archivo. Los
prototipos y la informacin relacionada necesaria est en el archivo <io.h>
open( ): Se usa para abrir un archivo. Regresa un descriptor de archivo -1 si falla. Se deben
incluir las libreras <fcntl.h> y <io.h>. Su prototipo es el siguiente:
open(char *archivo, modo);
El modo de acceso al archivo es el segundo parmetro, y tiene los siguientes
macros definidas en fcntl.h:
O_APPEND
O_CREAT
O_EXCL
O_RDONLY
O_RDWR
O_WRONLY
close( ): sirve para cerrar el archivo, y solo tiene un parmetro, el descriptor, al igual que fclose
tena el puntero. Fuerza al cacharro a escribir en disco cualquier informacin de las
memorias intermedias del disco del sistema operativo. Osea, que si falla se pierden los
datos.
creat ( ): es usada para crear un archivo y escribir en l. Su prototipo es:
creat(char *archivo, int modo);
En DOS, cada archivo tiene asociado un byte de atributo que especifica diversos
bits de informacin. En la siguiente tabla se presentan los valores que puede tomar:
Bit
0
1
2
3
4
5
...
Valor
1
2
4
8
16
32
...
Significado
read ( ): tiene los mismos argumentos que write, solo que ahora pondr el dato ledo en la memoria
intermedia (apuntada por buffer). Devuelve o bien los caracteres ledos o 1 si falla.
read( int fd, char *buffer, int longitud);
lseek ( ): proporciona el acceso directo a ficheros de disco. Es como el fseek, con los mismos
macros (SEEK_SET, etc), devuelve o la longitud o 1 si falla, y su prototipo es:
lseek ( int fd, int longitud, int macro);
unlink ( ): elimina un archivo. Devuelve 1 si falla. Su prototipo es:
unlink ( char *archivo );
Como ya llevamos vistas muchas cosas nuevas, un ejemplillo y a ver si se entiende.
#include <stdio.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#define LONGITUD 128
int escribir(char *, int);
int abrir(char *, int);
main( )
{
char buffer[LONGITUD];
int fd1, fd2;
fd1 = creat("mi_fichero", O_WRONLY);
escribir(buffer, fd1);
close(fd1);
fd2 = creat("mi_fichero", O_RDONLY);
abrir(buffer, fd2);
close(fd2);
}
int escribir(char *buffer, int fd1)
{
register int t;
for(t = 0; t < LONGITUD; t++)
buffer[t] = '\0';
printf("Escribe una frase: ");
gets(buffer);
write(fd1, buffer, LONGITUD);
}
int abrir (char *buffer, int fd2)
{
read(fd2, buffer, LONGITUD);
printf("%s", buffer);
}
NOTA: nunca trates de mezclar los dos sistemas de Entrada / Salida (con y sin
almacenamiento intermedio) en el mismo programa. La manera en que trata cada mtodo los
archivos es diferente, y podra interferir accidentalmente.
Redireccionamiento: Cuando por ejemplo ejecutamos el comando dir se nos muestra el resultado
en la pantalla. Sin embargo podemos hacer que el resultado se guarde en un fichero haciendo:
dir > resultado.txt
De esta forma el listado de directorios quedar guardado en el fichero resultado.txt y no se
mostrar en la pantalla. Esto es lo que se llama redireccin. En este ejemplo lo que hemos hecho
ha sido redireccionar la salida utilizando '>'.
La salida del programa se dirige hacia la salida estndar (stdout, standard output).
Normalmente, si no se especifica nada, la salida standard es la pantalla. La entrada de datos al
programa se hace desde la entrada estndar (stdin, standard input). Normalmente, por defecto es
el teclado.
Existe una tercera opcin que es la salida de error estndar (stderr, standard error). Aqu
es donde se muestran los mensajes de error del programa al usuario. Normalmente suele ser la
pantalla, al igual que la de salida. Por qu tenemos stderr y stdout? Porque de esta forma
podemos redireccionar la salida a un fichero y an as podemos ver los mensajes de error en
pantalla. Posiblemente tambin puedas controlar el stdaux (primer puerto en serie COM1) y el
stdprn (impresora).
En realidad stdin, stdout y stderr hay que verlos como ficheros:
-Si stdout es la pantalla, cuando usemos printf, el resultado se muestra en el monitor de nuestro
ordenador. Podemos imaginar que stdout es un fichero cuyo contenido se muestra en la pantalla.
-Si stdin es el teclado imaginemos que lo que tecleamos va a un fichero cuyo contenido lee el
programa.
Redireccionar la salida: ya hemos visto cmo se redirecciona la salida con el dir. Ahora vamos a
aplicarlo a nuestro curso con un sencillo ejemplo.
Vamos a ver un programa que toma los datos del teclado y los muestra en la pantalla. Si el
usuario teclea 'salir' el programa termina:
Como ya hemos visto antes
stderr es un fichero. stderr es la pantalla,
as que, si escribimos el texto en el
fichero stderr lo podremos ver en el
monitor. Si ejecutamos el programa
como hemos hecho hasta ahora
tendremos el siguiente resultado (en
negrita lo que tecleamos nosotros):
primera lnea
primera lnea
segunda
segunda
salir
salir
El usuario ha tecleado 'salir'
#include <stdio.h>
#include <string.h>
int main( )
{
char texto[100];
while ( strcmp(texto, "salir") != 0 )
{
gets(texto);
printf( "%s\n",texto );
}
fprintf( stderr, "El usuario ha tecleado 'salir'" );
}
Como vemos el programa repite lo que tecleamos. Si ahora utilizamos la redireccin '>
resultado.txt' el resultado por pantalla ser (en negrita lo que tecleamos nosotros):
primera lnea
segunda
esto es la monda
salir
El usuario ha tecleado 'salir'
Se habr creado un fichero llamado resultado.txt cuyo contenido ser:
primera lnea
segunda
esto es la monda
Si no hubisemos usado stderr el mensaje final hubiese ido a stdout (y por lo tanto al
fichero), as que no podramos haberlo visto en la pantalla. Hubiera quedado en el fichero
resultado.txt.
Redireccionar la salida con >>: Existe una forma de redireccionar la salida de forma que se aada
a un fichero en vez de sobreescribirlo. Para ello debemos usar '>>' en vez de '>'. Haz la siguiente
prueba:
dir > resultado.txt
dir >> resultado.txt
Tendrs el listado del directorio dos veces en el mismo fichero. La segunda vez que
llamamos al comando dir, si usamos >>, se aade al final del fichero.
Redireccionar la entrada: Ahora vamos a hacer algo curioso. Vamos a crear un fichero llamado
entrada.txt y vamos a usarlo como entrada de nuestro programa. Vamos a redireccionar la entrada
al fichero entrada.txt. El fichero entrada.txt debe contener lo siguiente:
Esto no lo he tecleado yo.
Se escribe slo.
Qu curioso.
Salir
Para cambiar la entrada utilizamos el smbolo '<'. Si nuestro programa lo hemos llamado
stdout.c haremos:
# stdout < entrada.txt
y tendremos:
primera lnea
segunda
esto es la monda
El usuario ha tecleado 'salir'
Increble, hemos escrito todo eso sin tocar el teclado. Lo que sucede es que el programa
toma los datos del fichero entrada.txt en vez del teclado.
Cada vez que encuentra un salto de lnea (el final de una lnea) es equivalente a cuando
pulsamos el <Intro>.
Podemos incluso hacer una doble redireccin: Tomaremos como entrada entrada.txt y
como salida resultado.txt:
stdout < entrada.txt > resultado.txt
NOTA: Cambiando el orden tambin funciona: stdout > resultado.txt < entrada.txt
Redireccionar desde el programa: Existe
una forma de cambiar la salida estndar
desde el propio programa.
Esto
se
puede
conseguir
utilizando la funcin freopen (vista
anteriormente).
#include <stdio.h>
#include <string.h>
main( )
{
char texto[100];
freopen( "resultado.txt","w",stdout );
gets(texto);
do
{
printf( "%s\n",texto );
gets(texto);
}while ( strcmp(texto, "salir") != 0 );
Ordenar un fichero
De todas las cosas sobre esto que me he encontrado por Internet y en libros de
informtica, me quedo con muy poco que sea realmente til (o al menos fcil de entender).
Supongamos que tenemos un fichero que contiene solo registros de tipo de la estructura
que tenemos definida como proveedor.
Resulta que en alguna parte del programa necesitamos tener el fichero ordenado para, por
ejemplo, hacer un listado de proveedores por nombre. Pues lo que hacemos es llamar a la funcin
de ordenacin implementada debajo.
En ella, deberemos tener definida otra estructura exactamente igual que nos servir como
memoria temporal de un registro para compararlo con el siguiente e ir ordenndolos. La llamamos
temporal.
Antes de comenzar a ordenar el fichero, lo que hago es calcular su tamao con una serie
de funciones ya vistas, de manera que si es 0 o menor (qu tontera), salimos de la funcin con un
return y dejamos de ordenar.
En caso contrario, nos liamos a ordenarlo con el mtodo shell, tomando como campo de
referencia para ordenarlos el nombre del proveedor.
...
struct
{
long ID_prove;
char nombre[50];
char direccion[200];
char telefono[15];
char otros[200];
}proveedor;
...
void ordenar_provee (void)
{
struct
{
long ID_prove;
char nombre[50];
char direccion[200];
char telefono[15];
char otros[200];
}temporal;
long int I, J, D, N = 0;
int Ok = 0;
FILE *puntero;
puntero = fopen(PROVEE, "rb");
fseek(puntero ,0L, SEEK_END);
N = ftell(puntero);
fclose(puntero);
if(N <= 0)
return;
I = (N / sizeof(proveedor)) - 1;
puntero = fopen(PROVEE, "r+b");
D = I;
do
{
D = D / 2;
do
{
Ok = 0;
for(J = 0; J <= I - D; J++)
{
fseek(puntero, J * sizeof(proveedor), SEEK_SET);
fread(&proveedor, sizeof(proveedor), 1, puntero);
fseek(puntero, (J + D) * sizeof(proveedor), SEEK_SET);
fread(&temporal, sizeof(proveedor), 1, puntero);
if (strcmpi(proveedor.nombre, temporal.nombre) > 0)
{
fseek(puntero, J * sizeof(proveedor), SEEK_SET);
fwrite(&temporal, sizeof(proveedor), 1, puntero);
fseek(puntero, (J + D) * sizeof(proveedor), SEEK_SET);
fwrite(&proveedor, sizeof(proveedor), 1, puntero);
Ok = 1;
}
}
}while (Ok);
}while (D != 1);
fclose(puntero);
}
Algoritmos establecidos
/* Nmero ledo */
/* Almacenamiento temporal */
ahex(a);
ahex(b);
}
void ahex(int n)
{
if ((n >= 0) && (n <= 9))
printf("%i",n);
else
{
switch(n)
{
case 10 : printf("A"); break;
case 11 : printf("B"); break;
case 12 : printf("C"); break;
case 13 : printf("D"); break;
case 14 : printf("E"); break;
case 15 : printf("F"); break;
default : printf("Error!\n");
}
}
}
#include <stdio.h>
#include <string.h>
#define NUM 5
main()
{
291
325
330
333
La primera estructura tendr un puntero que apunte a la segunda, sta tendr otro que
apunte a la tercera y as hasta llegar a la ltima, cuyo puntero apuntar a NULL. Es complejo de
entender, y a m me cost sudar varias camisetas.
n1
Null
56F1:02DE
&n1
568F:2044
ptr
568F:2044
*ptr
Null
561F:02DE
p1
Null
561F:02DE
p2
561F:02D2
561F:0266
Al final la cosa quedara as, suponiendo que la lista enlazada tuviera las letras q-w-e-r:
...
void insertar_al_principio(LISTA **ptr, char item)
{
LISTA *new;
new = malloc(sizeof(LISTA));
if(new != NULL)
{
new->dato = item;
new->enlace = *ptr;
*ptr = new;
}
}
void insertar_al_final(LISTA *ptr, char item)
{
LISTA *new;
while(ptr->enlace != NULL)
ptr = ptr->enlace;
new = malloc(sizeof(LISTA));
if(new != NULL)
{
ptr->enlace = new;
new->dato = item;
new->enlace = NULL;
}
}
BORRAR
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo
{
char dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
void eliminar_por_el_principio(LISTA **ptr);
void eliminar_por_el_final(LISTA **ptr);
main( )
{
LISTA *n1, *n2, *n3, *n4;
n1 = malloc(sizeof(LISTA));
n2 = malloc(sizeof(LISTA));
n3 = malloc(sizeof(LISTA));
n4 = malloc(sizeof(LISTA));
...
...
n1->dato = 'a';
n1->enlace = n2;
n2->dato = 'b';
n2->enlace = n3;
n3->dato = 'c';
n3->enlace = n4;
n4->dato = 'd';
n4->enlace = NULL;
printf("La lista enlazada es como sigue: ");
mostrar_lista(n1);
eliminar_por_el_principio(&n1);
printf("La nueva lista enlazada es: ");
mostrar_lista(n1);
eliminar_por_el_final(&n1);
printf("La nueva lista enlazada es: ");
mostrar_lista(n1);
}
void mostrar_lista(LISTA *ptr)
{
while(ptr != NULL)
{
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
void eliminar_por_el_principio(LISTA **ptr)
{
LISTA *p;
p = *ptr;
if(p != NULL)
{
p = p->enlace;
free(*ptr);
}
*ptr = p;
}
void eliminar_por_el_final(LISTA **ptr)
{
LISTA *p1, *p2;
p1 = *ptr;
if(p1 != NULL)
{
if(p1->enlace == NULL)
{
free(*ptr);
*ptr = NULL;
}
else
{
while(p1->enlace != NULL)
{
p2 = p1;
p1 = p1->enlace;
}
p2->enlace = NULL;
free(p1);
}
}
}
BUSCAR
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo
{
char dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
int buscar_en_lista(LISTA *ptr, char item);
main( )
{
LISTA *n1, *n2, *n3, *n4;
char item;
int encontrado;
n1 = malloc(sizeof(LISTA));
n2 = malloc(sizeof(LISTA));
n3 = malloc(sizeof(LISTA));
n4 = malloc(sizeof(LISTA));
n1->dato = 'a';
n1->enlace = n2;
n2->dato = 'b';
n2->enlace = n3;
n3->dato = 'c';
n3->enlace = n4;
n4->dato = 'd';
n4->enlace = NULL;
printf("La lista enlazada es como sigue: ");
mostrar_lista(n1);
printf("Introduzca un caracter a buscar en la lista: ");
item = getchar();
encontrado = buscar_en_lista(n1,item);
printf("\nEl caracter %c ",item);
encontrado ? printf(" ha ") : printf(" no ha ");
printf("sido encontrado en la lista: ");
mostrar_lista(n1);
}
void mostrar_lista(LISTA *ptr)
{
while(ptr != NULL)
{
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}
...
Ms sobre C
C y ASCII
Con C podemos trabajar con todos los caracteres que tiene el cdigo ASCII. Por detrs he
adjuntado una tabla con los caracteres no imprimibles (de control) e imprimibles hasta el 127.
#include <stdio.h>
#include <conio.h>
main( )
{
char caracter = 0x20;
int contador;
char letra1 = a;
char letra2 = 0x61;
Esto se debe a que el carcter 61
(contando en hexadecimal, ya que en
decimal es el 97) del cdigo ASCII
corresponde a la a.
El ejemplo imprime en pantalla los caracteres de tu tabla ASCII desde el carcter 32 hasta
el 255 con el contador contando en decimal, claro. Si la variable carcter la inicializsemos con el
valor 0x00 y el contador con 0, tambin intentara mostrar los caracteres de control, aunque fijo que
te salen como cuadraditos negros.
Otra forma ms sencilla sera declarando la variable como
int caracter = 33;
entera y cargarla por ejemplo con el valor decimal 33. Si luego
printf(La variable contiene);
pedimos que nos muestre por pantalla el contenido de la
printf("%c ", caracter);
variable, pero como carcter, nos devuelve el carcter que
corresponde al numero decimal 33 en la tabla ASCII (el !);
Ahora el reto es algo ms complicado: vamos a sacarnos un programa que convierta una
letra de minscula a mayscula, pero movindonos por la tabla ASCII.
En el ejemplo cargo a la variable n1 con el valor
hexadecimal 7A que corresponde a la z. La saco por
pantalla con la printf para que se vea que no hay truco.
En la siguiente operacin lo que hago es volcar
en n1 el resultado de pasar su contenido por un AND
binario. Ese 0xDF es en decimal un 223 y el 0x7A de la
z es en decimal un 122, y si los sumas da 345, pero
como solo hay 255 caracteres en la tabla ASCII, esto nos
lleva al 90, que casualmente es la Z.
#include <stdio.h>
main( )
{
int n1 = 0x7A;
printf("%c\n", n1);
n1 = n1 & 0xdf;
printf("%c",n1);
}
El truco de todo esto est en que el cdigo de cualquier letra minscula se puede
transformar a mayscula poniendo el quinto bit como 0, lo cual puede hacerse haciendo ese AND
binario con la mscara 0xDF que contiene un 0 en la posicin correspondiente al quinto bit.
Demasiado rebuscado, no? Abajo hago una comparacin para demostrar esta teora:
01100001
01000001
0x61
0x41
a
A
Carcteres no imprimibles
Nombre
Carcteres imprimibles
Nulo
00 NUL
32
20 Espacio
64
40
96
60
Inicio de cabecera
01 SOH
33
21
65
41
97
61
Inicio de texto
02 STX
34
22
"
66
42
98
62
Fin de texto
03 ETX
35
23
67
43
99
63
Fin de transmisin
04 EOT
36
24
68
44
100
64
enquiry
05 ENQ
37
25
69
45
101
65
acknowledge
06 ACK
38
26
&
70
46
102
66
Campanilla (beep)
07 BEL
39
27
'
71
47
103
67
backspace
08 BS
40
28
72
48
104
68
Tabulador horizontal
09 HT
41
29
73
49
105
69
Salto de lnea
10
0A LF
42
2A
74
4A
106
6A
Tabulador vertical
11
0B VT
43
2B
75
4B
107
6B
Salto de pgina
12
0C FF
44
2C
76
4C
108
6C
Retorno de carro
13
0D CR
45
2D
77
4D
109
6D
Shift fuera
14
0E SO
46
2E
78
4E
110
6E
Shift dentro
15
0F SI
47
2F
79
4F
111
6F
16
10 DLE
48
30
80
50
112
70
Control dispositivo 1
17
11 DC1
49
31
81
51
113
71
Control dispositivo 2
18
12 DC2
50
32
82
52
114
72
Control dispositivo 3
19
13 DC3
51
33
83
53
115
73
Control dispositivo 4
20
14 DC4
52
34
84
54
116
74
neg acknowledge
21
15 NAK
53
35
85
55
117
75
Sincronismo
22
16 SYN
54
36
86
56
118
76
23
17 ETB
55
37
87
57
119
77
Cancelar
24
18 CAN
56
38
88
58
120
78
Fin medio
25
19 EM
57
39
89
59
121
79
Sustituto
26
1 SUB
58
3A
90
5A
122
7A
Escape
27
1B ESC
59
3B
91
5B
123
7B
Separador archivos
28
1C FS
60
3C
<
92
5C
124
7C
Separador grupos
29
1D GS
61
3D
93
5D
125
7D
Separador registros
30
1E RS
62
3E
>
94
5E
126
7E
Separador unidades
31
1F US
63
3F
95
5F
127
7F DEL
Nota 1: dada la variable letra de tipo char e inicializada con el valor b, podemos pasar a la letra
siguiente diciendo: letra++; de forma que en letra se carga una c.
Nota 2: Suele pasar que como casi
#include <stdio.h>
toda la informtica est desarrollada
en ingls, algunos caracteres como ,
main( )
, , , y dems no salgan
{
correctamente en los letreros de
fflush(stdin);
funciones como printf, puts, etc, sobre
printf("\xA8 \bCrees que el Bar\x87 \ba merece estar en
todo al trabajar en MS-DOS.
primera divisi\xA2n?");
getchar( );
Esto se arregla indicando
dentro del letrero en vez de la letra el
carcter ASCII en hexadecimal al que
corresponde. Es como lo hago en el
ejemplo.
fflush(stdin);
printf("\n\xA8Gibraltar espa\xA4 \bol?");
getchar( );
}
Lo nico raro es que si por ejemplo pongo \xA8 que es el carcter , si despus le sigue
alguna letra usada en la numeracin hexadecimal (de la A a la F) nos dar un error diciendo que el
valor hexadecimal es demasiado largo. Lo arreglamos metiendo un espacio entre medias y luego
poniendo el retroceso (\b).
Argumentos de main
Como funcin que es, main tambin puede aceptar argumentos, y los tiene limitados a dos.
Esto sirve, por ejemplo, para decirle una serie de valores (claves secretas, por ejemplo) cuando
ejecutamos un programa. Se escribe as:
void main( int argc, char *argv [ ] )
El primer argumento es argc (argument count). Es de tipo int e indica el nmero de
argumentos que se le han pasado al programa.
El segundo es argv (argument values). Es un array de strings (o puntero a puntero a char).
En el se almacenan los parmetros. Normalmente (como siempre depende del compilador) el
primer elemento (argv[0]) es el nombre del programa con su ruta o path. El segundo (argv[1]) es el
primer parmetro, el tercero (argv[2]) el segundo parmetro y as hasta el final.
A los argumentos de main se les suele llamar siempre as. No es necesario, pero es una
vieja costumbre de los gurs programadores.
Para pasarle los argumentos al ejemplo de ms abajo hay tres formas que yo conozca.
Para ello deberemos haberlo compilado antes y haber obtenido un ejecutable llamado por ejemplo
prog_1.exe, el cual colocaremos en el directorio raz C:\.
-MS-DOS: damos la siguiente orden: C:\>prog_1.exe MARTIN LOPEZ SANCHEZ
Lo primero de todo es el path o direccin en la que est lo que queremos abrir. Este ser
siempre el primer argumento. Despus lo que hacemos es meter separados por espacios todos los
argumentos que queramos. Estos se guardarn en la memoria RAM y los usaremos para lo que
queramos.
-Windows: pulsando el botn <Inicio> de la barra de inicio, aparecer una opcin llamada
<Ejecutar>. Nos aparecer una ventana que nos pedir el path del programa, carpeta, documento
o recurso de Internet que deseamos abrir.
Ponemos por ejemplo: C:\prog_1.exe MARTIN LOPEZ SANCHEZ, con lo cual ocurrir
exactamente lo mismo que antes.
Nota: En los dos casos anteriores, si en vez de eso tecleamos por ejemplo:
c:\prog_1.exe "MARTIN LOPEZ SANCHEZ"
En ese caso tomar toda la cadena de caracteres contenida entre comillas como un solo
argumento.
-Borland C++ v 4.02: copiando el cdigo fuente en su editor de texto, antes de darle al botn del
rayo para sacar el ejecutable debemos irnos a <Options>, y pulsar la opcin Environment.
#include <stdio.h>
main(int argc, char *argv[])
{
int contador;
printf("El numero de argumentos es %d\n", argc);
for(contador = 0; contador < argc; contador++)
printf("El argumento %d es %s\n", contador, argv[contador]);
}
Aceptas y pulsas el rayo, y vers que los argumentos han sido pasados.
Reportando errores
En muchas ocasiones es til reportar los errores en un programa de C. La funcin de la
biblioteca estndar perror es la indicada para hacerlo. Es usada conjuntamente con la variable
errno y frecuentemente cuando se encuentra un error se desea terminar el programa antes.
perror( ):su prototipo es:
void perror(const char *s);
La funcin perror( ) produce un mensaje que va a la salida estndar de errores,
describiendo el ltimo error encontrado durante una llamada al sistema o a ciertas funciones de
biblioteca. La cadena de caracteres s que se pasa como argumento, se muestra primero, luego un
signo de dos puntos y un espacio en blanco; por ltimo, el mensaje y un salto de lnea.
Para ser de ms utilidad, la cadena de caracteres pasada como argumento debera incluir
el nombre de la funcin que incurri en el error. El cdigo del error se toma de la variable externa
errno, que toma un valor cuando ocurre un error, pero no es puesta a cero en una llamada no
errnea.
errno: A la variable especial del sistema errno algunas llamadas al sistema (y algunas funciones de
biblioteca) le dan un valor entero, para indicar que ha habido un error. Esta variable esta definida
en la cabecera #include <errno.h> y para ser usada dentro de un programa debe ser declarada de
la siguiente forma:
extern int errno;
El valor slo es significativo cuando la llamada devolvi un error (usualmente -1), algunas
veces una funcin tambin puede devolver -1 como valor vlido, por lo que se debe poner errno a
cero antes de la llamada, para poder detectar posibles errores.
El Preprocesador
El preprocesamiento es el primer paso en la etapa de compilacin de un programa (esta
propiedad es nica del compilador de C). El preprocesador tiene ms o menos su propio lenguaje
el cual puede ser una herramienta muy poderosa para el programador. Todas las directivas de
preprocesador o comandos inician con una # (almohadilla). Algunas ya estn vistas de antes.
#define: se usa para definir constantes o cualquier sustitucin de macro. Su formato es el
siguiente:
#define <nombre de macro> <nombre de reemplazo>
Por ejemplo:
#define FALSO 0
#define VERDADERO !FALSO
La directiva #define tiene otra poderosa caracterstica: el nombre de macro puede
tener argumentos. Cada vez que el compilador encuentra el nombre de macro, los
argumentos reales encontrados en el programa reemplazan los argumentos asociados con
el nombre de la macro. Por ejemplo:
#include "archivo"
Cuando se indica <archivo> se le dice al compilador que busque donde estn los
archivos incluidos o carpeta include del sistema. Si se usa la forma "archivo" es buscado
en el directorio actual, es decir, donde el programa esta siendo ejecutado.
Los archivos incluidos usualmente contienen los prototipos de las funciones y las
declaraciones de los archivos cabecera (header files) y no tienen cdigo de C (algoritmos).
#if: evalua una expresin constante entera.
Siempre se debe terminar con #endif
para delimitar el fin de esta sentencia.
Se pueden as mismo evaluar otro
cdigo en caso se cumpla otra condicin, o bien,
cuando no se cumple ninguna usando #elif o
#else respectivamente.
Como el ejemplo est claro como todo
en estos apuntes, creo que mejor no lo explico.
Lo nico que voy a hacer es rellenar este hueco
tan feo que se me queda.
#define MEX 0
#define EUA 1
#define ESP 2
#define PAIS_ACTIVO ESP
#if PAIS_ACTIVO = = MEX
char moneda[] = "pesos";
#elif PAIS_ACTIVO = = EUA
char moneda[] = "dolar";
#else
char moneda[] = "euro";
#endif
#ifdef (si definido) y #ifndef (si no definido): Otro mtodo de compilacin condicional es usar estas
directivas. El formato general de #ifdef es:
#ifdef <nombre de macro>
<secuencia de sentecias>
#endif
Si el nombre de macro ha sido definido en una sentencia #define, se compilar la
secuencia de sentecias entre el #ifdef y #endif. Luego, el formato general de #ifndef es:
#ifndef <nombre de macro>
<secuencia de sentecias>
#endif
#error: fuerza al compilador a parar la compilacin cuando la encuentra. Se usa principalmente
para depuracin.
#line numero cadena : informa al preprocesador cual es el nmero siguiente de la lnea de
entrada. El parmetro cadena es opcional y nombra la siguiente lnea de entrada. Esto es
usado frecuentemente cuando son traducidos otros lenguajes a C. Por ejemplo, los
mensajes de error producidos por el compilador de C pueden referenciar el nombre del
archivo y la lnea de la fuente original en vez de los archivos intermedios de C.
#pragma: es una instruccin de prepocesador que nos servir para indicar directivas a ste. Se
emplea de la siguiente manera:
#pragma <directiva>
Por ejemplo podemos utilizar
argsused que le indicar que es posible
que no empleemos todos los argumentos
que le pasamos a la funcin.
#pragma argsused
void main( int argc, char **argv );
{
. . .
}
Abortar un programa
Esta funcin lo que hace es detener (finalizar)
el programa en cuanto se la llama.
#include <stdio.h>
#include <stdlib.h>
main( )
{
printf("Llamando a abort()");
abort( );
}
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
main( )
{
char c;
printf("El usuario desea llamar a assert (s/n)?");
c = getch( );
assert(c != 'n');
...
printf("Instalando...");
printf("\nCreando carpetas del programa...");
system("md c:\\Mi_prog");
system("md c:\\Mi_prog\\graphics");
printf("\nCopiando ficheros graficos...");
system("copy graphics c:\\Mi_prog\\graphics");
system("attrib c:\\Mi_prog\\graphics +h +r");
sleep(3);
...
0
1
2
3
4
5
6
7
8
LIGHTBLUE
LIGHTGREEN
LIGHTCYAN
LIGHTRED
LIGHTMAGENTA
YELLOW
WHITE
BLINK
main( )
{
window(10, 10, 80, 25);
9
10
11
12
13
14
15
128
textcolor(4);
textbackground(7);
cprintf("Estoy encerrao!");
getch( );
}
#include <conio.h>
#include <stdio.h>
main( )
{
int i, j;
for (i=0; i<9; i++)
{
for (j=0; j<80; j++)
cprintf("C");
textcolor(i+1);
textbackground(i);
}
}
#include <conio.h>
#include <stdio.h>
#include <string.h>
main( )
{
char *password, correcto[] = "clave";
password = getpass("Poner aqui clave: ");
if(!strcmp(password, correcto))
printf("\nSe te ve astuto\\a");
}
#include <conio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define CLAVE "abcd"
main( )
{
char intento[11] = "\0";
int centinela = 0;
while(centinela < 3)
{
int contador;
char letra;
printf("\n\t\tIntroduzca la clave y pulse <INTRO>");
printf("\n\n\t\tClave (hasta 10 caracteres) => ");
Si
pulsa
<Intro>,
le
ponemos al final de la cadena un
freno y al centinela un valor mayor
de 3 y salimos del bucle de
capturar gracias a la funcin
continue.
if(letra = = '\b')
{
contador = contador - 2;
printf("\b \b");
continue;
}
intento[contador] = letra;
putchar('*');
Si pulsa el retroceso,
retrocede con el \b, imprime un
espacio para borrar el anterior
asterisco y vuelve a retroceder
para que sigamos pulsando
nuevas teclas.
}
if(strcmpi(CLAVE, intento))
{
printf("\n\t\tClave incorrecta");
getch();
clrscr();
centinela++;
}
else
{
printf("\n\t\tCorrecto");
exit(1);
}
if(centinela = = 3)
exit(1);
}
}
#include <dos.h>
#include <conio.h>
main( )
{
sound(1500);
delay(250);
nosound( );
getch( );
}
Nota: tambin podemos usar la funcin sleep ( ), tambin incluida en dos.h, para retardar, solo que
sta trabaja con segundos como argumento.
Lo siguiente que incluyo es un listado de las frecuencias y la nota musical a la que
corresponden. Con esto podramos generar una librera llamada notas.h:
#define do1
#define dos1
#define re1
#define res1
#define mi1
#define fa1
#define fas1
#define sol1
#define sols1
#define la1
#define las1
#define si1
66
70
73
78
82
86
91
96
102
108
115
122
/* do primera octava */
/* do # primera octava */
/* re primera octava */
/* re # primera octava */
/* mi primera octava */
/* fa primera octava */
/* fa # primera octava */
/* sol primera octava */
/* sol # primera octava */
/* la primera octava */
/* la # primera octava */
/* si primera octava */
#define do2
#define dos2
#define re2
#define res2
#define mi2
#define fa2
#define fas2
#define sol2
#define sols2
#define la2
#define las2
#define si2
130
139
148
156
164
176
188
198
209
220
233
247
/* do segunda octava */
/* do # segunda octava */
/* re segunda octava */
/* re # segunda octava */
/* mi segunda octava */
/* fa segunda octava */
/* fa # segunda octava */
/* sol segunda octava */
/* sol # segunda octava */
/* la segunda octava */
/* la # segunda octava */
/* si segunda octava */
#define do3
#define dos3
#define re3
#define res3
264
281
297
313
/* do tercera octava */
/* do # tercera octava */
/* re tercera octava */
/* re # tercera octava */
#define mi3
#define fa3
#define fas3
#define sol3
#define sols3
#define la3
#define las3
#define si3
330
352
374
396
415
440
468
495
/* mi tercera octava */
/* fa tercera octava */
/* fa # tercera octava */
/* sol tercera octava */
/* sol # tercera octava */
/* la tercera octava */
/* la # tercera octava */
/* si tercera octava */
#define do4
#define dos4
#define re4
#define res4
#define mi4
#define fa4
#define fas4
#define sol4
#define sols4
#define la4
#define las4
#define si4
528
565
594
625
660
704
748
792
836
880
935
990
/* do cuarta octava */
/* do # cuarta octava */
/* re cuarta octava */
/* re # cuarta octava */
/* mi cuarta octava */
/* fa cuarta octava */
/* fa # cuarta octava */
/* sol cuarta octava */
/* sol # cuarta octava */
/* la cuarta octava */
/* la # cuarta octava */
/* si cuarta octava */
Vistas ya las notas musicales, aqu pongo un listado de las posibles duraciones de las
notas (en segundos). Con esto nos podramos hacer otra librera duracin.h:
float n = 0.5;
float b = 2 * n;
float r = 4 * n;
float c = n / 2.0;
float sc = n / 4.0;
float f = n / 8.0;
float sf = n / 16.0;
float np = n + (n / 2.0);
float bp = b + (b / 2.0);
float rp = r + (r / 2.0);
float cp = c + (c / 2.0);
float scp = sc + (sc / 2.0);
float fp = f + (f / 2.0);
float sfp = sf + (sf / 2.0);
float trn = 2 * n / 3.0;
float trc = 2 * c / 3.0;
float trsc = 2 * sc / 3.0;
Campos de bit
Al contrario de la mayora de los lenguajes de programacin, C tiene un mtodo predefinido
para acceder a un nico bit en un byte.
El mtodo que C usa para acceder a los bits
se basa en la estructura. Un campo de bit es un tipo
especial de estructura que define la longitud en bits
que tendr cada elemento.
El formato general de una definicin de
campo de bit es como en el ejemplo de la derecha.
struct nomb_estruct
{
tipo nombre_1 : longitud;
tipo nombre_2 : longitud;
...
tipo nombre_n : longitud;
}
Se debe declarar un campo de bit como int, unsigned o signed. Se debe declarar los
campos de bits de longitud 1 como unsigned, ya que un bit nico no puede tener signo. Por
ejemplo, considerar esta definicin de estructura:
struct empaquetado
{
unsigned int b1:1;
unsigned int b2:1;
unsigned int b3:1;
unsigned int b4:1;
unsigned int tipo:4;
unsigned int ent_raro:9;
} paquete;
Nmeros aleatorios
Se trata de generar nmeros al azar, como en la lotera. Para ello C tiene una serie de
funciones que nos pueden valer para algunos programas tales como juegos, simulaciones y
experimentos.
Hay que tener en cuenta que ninguna funcin produce datos aleatorios verdaderos ya que
son calculados a partir de una frmula dada y las secuencias de nmeros que son producidas se
repiten.
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
main( )
{
for( ; ; )
{
printf("%i\n", random (100));
getch( );
}
En este de la
derecha,
lo
que
hacemos es obtener
un total de 100
nmeros aleatorios,
habiendo inicializado
el
generador
de
nmeros aletorios con
los valores de la
estructura time_t.
#include<stdlib.h>
#include<stdio.h>
#include<time.h>
main( )
{
int x;
int i;
int a = 0;
time_t t;
srand((unsigned) time(&t));
Por
cada
vuelta que da el bucle
for, suma en la
variable a todos los
valores obtenidos, de
manera que al salir de
l,
obtenemos
la
media de todos los
valores dividiendo por
100, y lo imprimimos
en
pantalla.
Este
ejemplo
es
muy
interesante, no?
Buscar un resultado:
En este programa, lo
que queremos es contar las
veces que necesita nuestro
ordenador
para
sacar
aleatoriamente un nmero, en
este caso ser el 53, en un
intervalo comprendido entre el
0 y el 100.
Es muy parecido al
anterior solo que ahora es un
bucle do while el que imprime
en pantalla un nmero
aleatorio mientras que ese
nmero sea distinto de 53,
contando las veces que lo
haca. Al sacar 53, imprimimos
por pantalla la posicin en
que sali. Vers como cada
vez es diferente.
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
main( )
{
int c=0;
int x;
time_t t;
srand((unsigned)time(&t));
do
{
x= rand() % ((100)+1);
c++;
printf("%d ",x);
}
while(x!=53);
printf("\nEl numero 53 ha salido en la posicion %i",c);
}
#include <stdio.h>
#include <dos.h>
struct time t;
main( )
{
gettime(&t);
printf("Son las %i:%i", t.ti_hour, t.ti_min);
}
Luego sacamos la hora por pantalla llamando a las variables de la estructura (las del
ejemplo).
Las interrupciones son eventos que producen que la CPU se detenga y pase a ejecutar
inmediatamente un determinada rutina. Frecuentemente, la comunicacin entre la CPU y los
perifricos se realiza por medio de interrupciones. Las interrupciones software son provocadas por
los programas usando una funcin especial del lenguaje. Tienen como objetivo el que la CPU
ejecute algn tipo de funcin. Al terminar de ejecutarse esta funcin, se seguir ejecutando el
programa que provoc la interrupcin.
Este tipo de interrupciones es la forma mas importante que tendrn los programas de
ejecutar funciones especiales del DOS (Disk Operating System) o del BIOS (Basic Input Output
System).
Una rutina de interrupcin suele utilizar algunos registros internos de la CPU, por lo que es
necesario asignarles los parmetros de entrada antes de ejecutar la rutina de servicio de
interrupcin, y leer los valores devueltos en los mismos tras la ejecucin de la misma.
El tamao de los registros suele ser de 16 bits. Algunos de ellos, como por ejemplo los
llamados AX, BX, CX y el DX, los podemos dividir en dos de 8 bits cada uno. Por ejemplo, El AX
puede descomponerse en AH y AL, siendo AH el que contiene la parte ms significativa o de
mayor orden (high) de AX, y el AL la de orden bajo (low).
Las llamadas a las interrupciones por software se pueden realizar en los lenguajes de alto
nivel mediante funciones ya implementadas. Nosotros vamos a utilizar una funcin llamada int86
que viene definida en el fichero dos.h de nuestro C. La sintaxis de dicha funcin1 es la siguiente:
int int86(nmero, entrada, salida);
Aqu, nmero es el nmero de interrupcin (nmero de
vector de interrupcin) que queremos llamar; en entrada se
especifican los valores de los registros antes de la llamada, y
en salida se obtienen los valores de los registros tras ser
ejecutada la rutina correspondiente. Tanto entrada como salida
se declaran como una union de tipo REGS que est definida
como en el recuadro de la derecha.
union REGS
{
struct XREGS
struct HREGS
}entrada, salida;
x;
h;
Es decir, se puede acceder a los registros de 16 bits, o bien a los de 8 bits individualmente.
Las estructuras XREGS y HREGS estn tambin definidas y son las siguientes.
struct XREGS
/* palabras */
{
unsigned int ax, bx, cx, dx;
unsigned int si, di, cflag, flags;
};
struct HREGS
/* bytes */
{
unsigned char al, ah, bl, bh;
unsigned char cl, ch, dl, dh;
};
En
el
siguiente
ejemplo
se
implementa una funcin que permite colocar
el cursor en una posicin determinada de la
pantalla (equivalente a gotoxy( ) de nuestro
C), usando la subfuncin 2 de la rutina de
interrupcin 10h de la BIOS (interrupcin de
video):
Como se puede apreciar se llama a la
interrupcin de video (10h). Para que sta se
ejecute correctamente se debe asignar al
registro ah el valor 2, que indica la subfuncin
que debe ejecutarse.
Esta subfuncin necesita conocer las
coordenadas x e y de pantalla donde se
desea colocar el cursor, que se pasan a
travs
de los
registros
dl y dh,
respectivamente.
En este caso, no es preciso devolver
ningn valor, as que los valores que tengan
los registros tras ejecutarse la interrupcin (si
es que se han modificado) no son relevantes.
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#define VIDEO 0x10
void movetoxy (char x, char y)
{
union REGS regs;
regs.h.ah = 2;
/*Numero de subfuncion*/
regs.h.dh = y;
/*Fila*/
regs.h.dl = x;
/*Columna*/
regs.h.bh = 0;
/*Pagina de video*/
int86(VIDEO, ®s, ®s);
}
main( )
{
clrscr( );
movetoxy(3,3);
printf("Hola");
getch( );
}
La interrupcin de teclado:
Para acceder a las funciones del teclado se usa la interrupcin de la BIOS 16h. Dicha
interrupcin permite acceder a varias rutinas distintas asignando al registro ah el nmero de
funcin o subrutina correspondiente. Vamos a describir brevemente algunas de ellas:
- Leer un carcter desde el teclado
Nmero de interrupcin: 16h
Nmero de funcin: 0
Entrada: AH = 0
Salida: AL: cdigo ASCII de la tecla pulsada
Nota: Mediante esta funcin es posible capturar una tecla del bfer del teclado, es decir, algo
similar a lo que hace getch( ).
- Deteccin de tecla pulsada
Nmero de interrupcin: 16h
Nmero de funcin: 1
Entrada: AH = 1
Salida: Zero-flag = 0 si hay una tecla en el buffer
Zero-flag = 1 el buffer est vaco
Nota: El flag del cero es el bit 6 del registro flags (el primer bit es el 0).
Mediante esta funcin se puede detectar si hay alguna tecla en el bfer del teclado,
pero no se extrae de el mismo. Es equivalente a kbhit().
- Estado del teclado
Nmero de interrupcin: 16h
Nmero de funcin: 2
Entrada: AH = 2
Salida: AL : estado del teclado
Nota: Segn los valores de los bits del registro AL se indica:
Bit
Bit
Bit
Bit
Bit
Bit
Bit
Bit
0:
1:
2:
3:
4:
5:
6:
7:
Para el manejo del ratn, Microsoft siempre considera que la pantalla est formada por un
total de 640 x 200 puntos, independientemente de su modo, y que las coordenadas del extremo
superior izquierdo de la pantalla son (0, 0). Su interrupcin se realiza con 0x33.
#include <stdio.h>
#include <dos.h>
main( )
{
union REGS registros;
registros.x.ax = 0;
/* Funcion */
int86 (0x33, ®istros, ®istros);
if (registros.x.ax = = 0xFFFF)
printf("Tienes raton instalado" );
else
printf("No Tienes raton instalado");
Entrada: AX = 0024h
Salida: AX = FFFFh: error
}
en caso contrario,
BH = nmero de versin mayor
BL = nmero de versin menor
CH = tipo (1=bus, 2=serial, 3=InPort, 4=PS/2)
CL = interrupcin (0=PS/2, 2=IRQ2, 3=IRQ3, ..., 7=IRQ7)
Nota: Si el nmero de versin del driver del ratn es, por ejemplo 8.48, en registro BH se devuelve
el valor 8 y en el registro BL el valor 48.
- Obtener informacin general sobre el driver del ratn
Entrada: AX = 0025h
Salida: AX = informacin general del driver
bit 15: tipo de driver
0: el driver se carg de un archivo .COM
1: el driver existe como un controlador de dispositivos y se incluy en el
archivo CONFIG.SYS
bits 13,12: tipo de cursor del ratn
00: cursor de texto tipo software (ver AX=000Ah)
01: cursor de texto tipo hardware (ver AX=000Ah)
1x: cursor grfico (ver AX=000Bh)
- Reset del hardware del ratn
Entrada: AX = 002Fh
- Mostrar cursor del ratn
Entrada: AX = 0001h
Nota: Esta funcin incrementa un contador interno que decide si el cursor del ratn aparece o no
en la pantalla. Si este contador contiene el valor 0, el cursor aparecer. Al realizar un reset del
driver del ratn (funcin 0000h) dicho contador empezar por el valor -1.
Efecto
.
parpadeo (blink)
color de fondo (background)
color del carcter (foreground)
cdigo ASCII del carcter
Nota: Un mickey es el menor incremento en la distancia recorrida fsicamente por el ratn que
puede medir el hardware del mismo.
En los ratones convencionales un mickey equivale a 1/200 pulgadas ( 1/400 pulgadas en
los ms modernos).
- Ajustar la sensibilidad del ratn
Entrada: AX = 001Ah
BX = nmero de mickeys que representan 8 puntos horizontalmente (ver
AX=000Fh)
CX = nmero de mickeys que representan 8 puntos verticalmente (ver AX=000Fh)
DX = umbral para velocidad doble (ver AX=0013h)
- Obtener la sensibilidad del ratn
Entrada: AX = 001Bh
Salida: BX = nmero de mickeys que representan 8 puntos horizontalmente (ver
AX=000Fh)
CX = nmero de mickeys que representan 8 puntos verticalmente (ver AX=000Fh)
DX = umbral para velocidad doble (ver AX=0013h)
- Obtener posicin del ratn y estado de los botones
Entrada: AX = 0003h
Salida: BX = estado de los botones
bit 0 =1: botn izquierdo pulsado
bit 1 =1: botn derecho pulsado
bit 2 =1: botn central pulsado (Mouse Systems/Logitech mouse)
CX = columna
DX = fila
- Obtener el nmero de veces que se puls un botn del ratn
Entrada: AX = 0005h
BX = botn
0000h izquierdo
0001h derecho
0002h central (Mouse Systems/Logitech mouse)
Salida: AX = estado de todos los botones
bit 0 =1: botn izquierdo pulsado
bit 1 =1: botn derecho pulsado
bit 2 =1: botn central pulsado (Mouse Systems/Logitech mouse)
BX = nmero de veces que el botn especificado ha sido pulsado desde la ltima
llamada a esta funcin
CX = columna donde se puls el botn especificado por ltima vez
DX = fila donde se puls el botn especificado por ltima vez
Nota: El controlador de eventos es una funcin que queda residente en memoria y que es llamada
por el driver del ratn cuando ocurre algn evento para el que ha sido programada.
La funcin llamada recibe los siguientes valores en los registros internos de la CPU:
AX = mscara del evento que ha ocurrido (misma asignacin de bits que en el registro CX en la
instalacin)
= 0018h: Ok
= FFFFh: error
Vamos a ver este ejemplo, que detiene a la CPU para que dibuje un cursor en la pantalla:
#include <stdio.h>
#include <conio.h>
#include <dos.h>
union REGS rin, rout;
main( )
{
int tab[] =
{
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
/*************************/
/*
Mascara de
*/
/*
Pantalla
*/
/*************************/
/*************************/
/*
Mascara de
*/
/*
cursor
*/
/*************************/
};
rin.h.ah = 0;
rin.h.al = 6;
int86 (0x10, &rin, &rout);
rin.x.ax = 0;
int86 (0x33, &rin, &rout);
/* Reinicia el raton */
rin.x.ax = 1;
int86 (0x33, &rin, &rout);
rin.x.ax = 9;
rin.x.bx = 0;
rin.x.dx = (unsigned int) tab;
int86 (0x33, &rin, &rout);
getch( );
La} interrupcin BIOS de video
Interrupciones de Video
La comunicacin con la tarjeta de video se puede realizar usando la interrupcin 10h.
Usando esta rutina de interrupcin es posible acceder a diversas subfunciones que afectan a la
salida de caracteres por pantalla. Algunas de ellas son las siguientes:
La Interrupcin de CD - ROM
Un CD est fsicamente compuesto por una serie de protuberancias y cavidades llamadas
pits y lands, que se alinean a lo largo de una nica espiral que va desde dentro hacia fuera y cubre
todo el CD. Vamos a ver algo de los tipos, para culturizarnos un poco.
CD de Audio: Un CD de Audio est concebido para una frecuencia de muestreo de 44.1 KHz con 2
canales (estreo) utilizando 16 bits para la codificacin de cada canal. El CD se divide segn el
estndar en sectores que corresponden a 1/75 segundos de reproduccin.
CD-ROM: El formato de un CD de datos (explicado en un tal libro rojo) o CD-ROM est basado en
el formato de CDs de audio. En un CD-ROM no se utilizan todos los 2352 bytes del sector fsico
como datos sino que se aprovechan parte de estos bytes para la deteccin y correccin de errores.
De esta forma, un sector fsico en un CD de datos tiene un tamao efectivo de 2048 bytes (2KB),
que es un tamao mucho ms fcil de manejar para un computador.
Para poder acceder a los datos almacenados, no en forma de sectores fsicos sino como
archivos y directorios, se precisa de un formato lgico. Para ello, existen dos estndares sobre el
formato lgico de un CD: el High Sierra Group (HSG) y la norma ISO 9660. Esencialmente se
distinguen en el nmero y tipo de caracteres permitidos para el nombre de un fichero y el formato
de la fecha y hora en la entrada de directorio de cada archivo.
En un CD-ROM, el sector lgico tiene un tamao de 2048 bytes (igual que el sector fsico
efectivo) y se direcciona de tal forma que el sector lgico 0 corresponde al sector fsico cuya
direccin es 00000200h, es decir, en el minuto 0, segundo 2 y offset 0. De esta forma, como cada
sector fsico corresponde a 1/75 segundos de reproduccin de un CD de audio, realmente el sector
lgico 0 empieza en el sector fsico 150 y los primeros 150 sectores fsicos de un CD-ROM no
pueden direccionarse desde el nivel de formato lgico. Esto se ve en la formulilla:
N Sector Lgico (minuto,segundo,offset) = (minuto*60+segundo)*75-150+offset
Acceso al CD a travs de MS-DOS: Los CD-ROM no se basan en una estructura de archivos tal y
como la conoce el MS-DOS ya que principalmente no poseen ninguna FAT. Los archivos y
directorios en un CD-ROM estn almacenados en sectores consecutivos; en un CD el proceso de
lectura se realiza siempre con velocidad lineal constante (y no con velocidad angular constante
como es el caso de los discos duros y disquetes) por lo que si el lector ptico tiene que leer un
sector alejado de la posicin actual se debe esperar a que la velocidad de rotacin sea la
adecuada para poder acometer el proceso de lectura; etc. Resumiendo, que no es lo mismo, por
que vaya royo.
As pues, el driver del CD-ROM (que se carga en el CONFIG.SYS mediante el comando
DEVICE) est definido como un controlador de dispositivo de caracteres (igual que el teclado o el
vdeo) pero el MS-DOS no asigna el nombre de una unidad (como A:, B:, etc.) para este tipo de
dispositivos. Por este motivo, tuvo que utilizarse un segundo programa que creara el vnculo
necesario entre el ncleo del DOS y el controlador de CD: MSCDEX.EXE.
El programa MSCDEX.EXE (que se carga en el fichero AUTOEXEC.BAT) engaa al MSDOS creando una unidad ficticia de red (de la que ya obtiene un nombre de unidad) y reencamina
todas las llamadas a dicha unidad a travs del controlador de CD.
Acceso al CD a travs de MSCDEX: este programa instala en la interrupcin 2Fh del MS-DOS una
serie de funciones a travs de las cuales se puede acceder al CD. Estas funciones se identifican
segn el valor que se asigne al registro AX antes de llamar a la interrupcin. Algunas de estas
funciones son las siguientes:
- Determinar el nmero de unidades CD-ROM
Entrada: AX = 1500h
BX = 0000h
Salida: BX = nmero de unidades gestionadas
CX = denominacin de la primera unidad (0 = A:, 1 = B:, etc.)
Nota: Si BX=0 en la salida querr decir que MSCDEX no est instalado ya que para que MSCDEX
est activo se necesita al menos una unidad de CD-ROM.
- Comprobar si una determinada unidad pertenece a una unidad CD-ROM
Entrada: AX = 150Bh
CX = nmero de unidad (0 = A:, 1 = B:, etc.)
Salida: BX = ADADh si MSCDEX.EXE instalado
AX
= 0000h: No se trata de una unidad CD-ROM
= 0000h: Es una unidad de CD-ROM
- Obtener el nmero de versin de MSCDEX
Entrada: AX = 150Ch
Salida: BH = nmero de versin mayor
BL = nmero de versin menor
Nota: Si el nmero de versin de MSCDEX es, por ejemplo 2.95, en registro BH se devuelve el
valor 2 y en el registro BL el valor 95.
- Acceder al CD a travs del controlador de dispositivos
Entrada: AX = 1510h
CX = Unidad de CD-ROM (0 = A:, 1 = B:, etc)
ES:BX Buffer con la funcin requerida al driver de CD-ROM
Nota: Mediante esta funcin MSCDEX ofrece la posibilidad de acceder directamente al controlador
de dispositivos que gestiona el CD-ROM asociado a la unidad lgica descrita en el registro CX.
El contenido de los campos del buffer al que apuntan los registros ES:BX es distinto segn
tipo de subfuncin que se requiera.
= 1: Unidad abierta
= 0: Unidad cerrada
bit 3
bit 4
bit 11
Si Estado
= -1: El CD ha sido cambiado desde la ltima vez que se llam a esta funcin
= 0: El controlador no sabe si ha habido un cambio o no desde la ltima vez
= 1: El CD no ha sido cambiado desde la ltima vez que se llam a esta funcin
= 0 pista de audio
= 1 pista de datos
Si el bit 0 del Estado de audio vale 1 quiere decir que la reproduccin actual se encuentra
en estado de pausa. En ese caso, en los siguientes campos podemos saber a partir de qu
sector se continuar la reproduccin del CD de audio si utilizamos el comando RESUME y en
qu sector se parar la reproduccin en dicho caso.
La interrupcin de la disquetera.
El proceso de dar formato a un disquete puede subdividirse en dos pasos claramente
diferenciados: el formato a bajo nivel y el formato a alto nivel.
La primera tarea, o formato de bajo nivel, incluye la definicin de pistas y sectores fsicos
en el disco. En este proceso, bsicamente se escriben las cabeceras de los distintos sectores con
objeto de que posteriormente se puedan localizar a la hora de escribir o leer informacin.
El segundo paso es el formato de alto nivel. En este caso, deben reservarse los primeros
sectores del disco y escribir sobre ellos la informacin necesaria para que el sistema operativo sea
capaz de localizar ficheros, directorios, zonas libres, etc. Este proceso implica la escritura del
sector de arranque, las 2 tablas de la FAT y los sectores de directorio.
La interrupcin 13h de la BIOS
proporciona una serie de funciones que
permiten realizar una serie de operaciones
sobre los discos, dependiendo del valor que
se asigne al registro AH antes de ser
invocada.
La bandera cflag se activa en caso de
producirse un error, en cuyo caso el tipo de
error se devuelve a travs del registro AH, el
cual puede tomar los valores de la tabla.
Cuando se ha detectado un error,
normalmente la operacin se debe reintentar
de 5 a 10 veces antes de abortar la operacin
y notificar dicho error.
Las funciones proporcionadas por la interrupcin 13h son las siguientes:
- Reset
Entrada: AH = 0h
DL = nmero de unidad
Salida: ninguna
Nota: Debe realizarse un Reset en caso de error antes de abortar la operacin que se est
llevando a cabo. El nmero de unidad es 0 en el caso de la unidad a:, 1 en el caso de b:, etc.
- Leer sectores
Entrada: AH = 2h
DL = nmero de unidad
DH = nmero de cabezal (0 = arriba, 1 = abajo).
CL = nmero de sector (1 a N)
CH = nmero de pista (0 a N-1)
AL = nmero de sectores a leer
ES:BX = direccin de memoria donde se almacenarn los datos ledos
Salida: AH = estado de error
AL = nmero de sectores ledos
cflag: activado en caso de error
Nota: En los registros ES y BX se especifica el segmento y el offset de un buffer donde se
transferirn los sectores ledos.
- Escribir sectores
Entrada: AH = 3h
DL = nmero de unidad
DH = nmero de cabezal (0 = arriba, 1 = abajo).
CL = nmero de sector (1 a N)
CH = nmero de pista (0 a N-1)
AL = nmero de sectores a escribir
ES:BX = direccin de memoria de donde se leern los datos a escribir
Salida: AH = estado de error
AL = nmero de sectores escritos
cflag: activado en caso de error
Nota: En los registros ES y BX se especifica el segmento y el offset de un buffer desde donde se
transferirn los datos a escribir.
- Verificar sectores
Entrada: AH = 4h
DL = nmero de unidad
DH = nmero de cabezal (0 = arriba, 1 = abajo).
CL = nmero de sector (1 a N)
CH = nmero de pista (0 a N-1)
AL = nmero de sectores a verificar
Salida: AH = estado de error
cflag: activado en caso de error
Nota: Se comprueba el CRC de los sectores considerados.
- Formatear pista
Entrada: AH = 5h
DL = nmero de unidad
DH = nmero de cabezal (0 = arriba, 1 = abajo).
AL = nmero de sectores/pista
CH = nmero de pista a formatear
ES:BX = puntero a la tabla de formatos
Salida: AH = estado de error
cflag: activado en caso de error
Nota: Con esta funcin se realiza el formato a bajo nivel de una pista. En los registros ES y BX se
debe especificar la direccin de la tabla de formatos, que contiene los datos que se escribirn en
las cabeceras de los correspondientes sectores.
Esta tabla es simplemente una matriz con tantos elementos como sectores tenga la pista,
donde cada elemento es un registro de 4 bytes con la siguiente estructura:
Byte 1: Nmero de pista
Byte 2: Nmero de cara (0 = arriba, 1 = abajo)
Byte 3: Nmero de sector (1 a N)
Byte 4: Nmero de bytes/sector (0 = 128 bytes, 1 = 256 bytes, 2 = 512 bytes, 3 = 1024 bytes)
Las Otras estructuras:
struct REGPACK
{
unsigned r_ax, r_bx, r_cx, r_dx;
unsigned r_bp, r_si, r_di, r_ds, r_es, r_flags;
};
struct SREGS
{
unsigned int es, cs, ss, ds;
};
Estas estructuras las vamos a usar con las siguientes funciones que tambin nos permiten
invocar las interrupciones:
int int86x (numero, entrada, salida, segmento);
void intr (numero, struct REGPACK *registros);
La primera funcin se basa en la declaracin de dos uniones (las mismas que int86( )): una
para entrada y otra para salida, que simbolizan los valores iniciales (antes de llamar a la
interrupcin) y finales (tras la llamada) en los registros. Vamos a ver un ejemplo para recordar:
...
union REGS regs;
regs.h.ah = 0;
regs.h.al = 0x13;
A la vuelta, dicha estructura habr sido modificada para indicar el valor devuelto en los
registros de segmento tras la interrupcin).
Hay quien prefiere trabajar con
REGPACK, que con una sola estructura
permite tambin operar con los registros de
segmento y la emplea tanto para enviar
como para recibir los resultados.
...
struct REGPACK bios;
bios.r_ax = 0x13; /*VGA 320x200 - 256 colores*/
intr (0x10, &bios);
/*cambiar modo de vdeo*/
...
El inconveniente, poco relevante, es que slo admite registros de 16 bits, lo que suele
obligar a hacer desplazamientos y forzar el empleo de mscaras para trabajar con las mitades
necesarias. La CPU construye la direccin real de la siguiente forma:
direccin real = segmento x 16 + desplazamiento
Programas residentes:
void keep (unsigned char errorlevel, unsigned tamao);
La funcin anterior, basada en el servicio 31h del DOS, permite a un programa realizado en
C quedar residente en la memoria. Adems del cdigo de retorno, es preciso indicar el tamao del
rea residente (en prrafos). Es difcil determinar con precisin la memoria que ocupa un programa
en C. Sin embargo, en muchos casos la siguiente frmula puede ser vlida:
keep (0, (_SS + ((_SP + area_de_seguridad)/16) - _psp));
En los casos en que no lo sea, se le puede hacer que vuelva a serlo aumentando el
tamao del rea de seguridad (que en los programas menos conflictivos ser 0). Tanto _psp como
_SS y _SP estn definidas ya por el compilador, por lo que la lnea anterior es perfectamente
vlida (sin ms) al final de un programa.
Variables predefinidas interesantes, disponibles en todo el programa:
_version
_osmajor
_osminor
_psp
_stklen
_heaplen
De estas variables predefinidas, las ms tiles son quiz las que devuelven la versin del
DOS, lo que ahorra el esfuerzo que supone averiguarlo llamando al DOS o empleando la funcin
de librera correspondiente. Tambin es til _psp, que permite un acceso a este rea del programa
de manera inmediata.
La sentencia asm( ):
La sentencia asm permite incluir cdigo ensamblador dentro del programa C. Sin embargo,
el uso de esta posibilidad est ms o menos limitado segn la versin del compilador.
#include <stdio.h>
main( )
{
int dato1, dato2, resultado;
printf("Dame dos nmeros: ");
scanf("%d %d", &dato1, &dato2);
asm
{
push ax; push cx;
mov cx,dato1
mov ax,0h
}
mult: asm
{
add ax,dato2
loop mult
mov resultado,ax
pop cx; pop ax;
}
printf("Su producto por el peor mtodo
da: %d", resultado);
La sentencia interrupt( ):
Con interrupt <declaracin_de_funcin>; se declara una determinada funcin como de tipo
interrupcin. En estas funciones, el compilador preserva y restaura todos los registros al comienzo
y final de las mismas; finalmente, retorna con IRET. Por tanto, es til para funciones que controlan
interrupciones. Para emplear esto, se debera compilar el programa con la opcin test stack
overflow y las variables tipo registro desactivadas.
Acceso a la memoria:
int peek (unsigned seg, unsigned off);
char peekb (unsigned seg, unsigned off);
void poke (unsigned seg, unsigned off, int valor);
void pokeb (unsigned seg, unsigned off, char valor);
unsigned FP_OFF (void far *puntero);
unsigned FP_SEG (void far *puntero);
void far *MK_FP (unsigned seg, unsigned off);
Las funciones peek( ), peekb( ), poke( ) y pokeb( ) tienen una utilidad evidente de cara a
consultar y modificar las posiciones de memoria. Cuando se necesita saber el segmento y/o el
offset de una variable del programa, las macros FP_OFF y FP_SEG devuelven dicha informacin.
Por ltimo, con MK_FP es posible asignar una direccin de memoria absoluta a un puntero far. Por
ejemplo, si se declara una variable:
char far *pantalla_color;
se puede hacer que apunte a la memoria de vdeo del modo texto de los adaptadores de color con:
pantalla_color = MK_FP (0xB800, 0);
y despus se podra limpiar la pantalla con un bucle:
for (i=0; i<4000; i++) *pantalla_color++=0;
INT 43:
INT 44-45:
INT 46:
INT 47-49:
INT 4A:
INT 4B-5F:
INT 60-66:
INT 67:
INT 68-6F:
INT 70:
INT 71:
INT 72:
INT 73:
INT 74:
INT 75:
INT 76:
INT 77:
INT 78-7F:
INT 80-85:
INT 86-F0:
INT F1-FF:
Grficos
Con el lenguaje C podemos generar grficos, imgenes y todo lo que se te ocurra. Todo
depende de la creatividad y la paciencia que uno tenga.
Cuando nosotros hacamos los programas, lo hacamos creando al compilarlo una
pantallita que estaba en modo texto de video. Esta pantalla est dividida en una serie de celdillas
en las cuales slo cabe un carcter (su dibujo) del cdigo ASCII.
En este captulo vamos a dejar atrs los programas en modo de texto para aprender a
dominar nuestras tarjetas grficas del cacharro. Nuestros programas sern mucho ms valorados
con una buena Interface Grfica (vamos, que le molarn ms al usuario).
Para la programacin en modo grfico usaremos el BGI o Borland Graphics Interface que
permite, mediante rdenes concretas del lenguaje que estamos utilizando, acceder a las funciones
de dibujo ms corrientes a partir de las cuales podemos empezar nuestro periplo por los modos
grficos. Pero antes unos conceptos para la sesera, muy comunes:
-VGA: (Video Graphics Array, matriz grfica de video) suele ser el circuito electrnico o tarjeta
que controla la informacin que aparece en la pantalla. Existen muchos tipos, y las que ahora ms
abundan son las tarjetas Sper VGA, que poseen caractersticas muy superiores al resto de las
dems tarjetas, pero cuya programacin se puede complicar un poquillo.
-Pxel: es el nombre que se le da a un punto de la pantalla, el cual tiene un color determinado y una
posicin. El conjunto de esos puntitos al hacerse pequeitos muestran por ejemplo la foto porno
que nos hemos bajado de Internet. La profundidad de color es una manera de expresar el nmero
de colores distintos que pueden tomar los pxeles.
La informacin de un pxel tambin se almacena en un taco de ceros y unos (binario) que
hace referencia al color y atributos especiales del mismo.
-Paleta: conjunto de colores que posee un modo grfico (2, 16, 256, 32.000, 16 millones...).
-Resolucin: nos indica la cantidad de pxeles que caben en la pantalla a lo ancho y a lo alto, como
por ejemplo 640 x 480 (307.200 pxeles). A mayor resolucin, los puntos sern ms pequeos y
conseguiremos una mayor calidad grfica, mientras que a resoluciones menores es ms fcil notar
el tamao de los puntos y la calidad ser menor. Aunque por otro lado, a menor resolucin y menor
nmero de colores, el programa resultante ser ms rpido.
En el modo texto las resoluciones generalmente son reducidas, como por ejemplo 80 x 25,
osea, 25 filas de 80 caracteres cada una que se puedan visualizar.
-13h: es el nombre de un modo grfico de la VGA (tambin compatible en la tarjeta SVGA) muy
18
conocido que puede representar 256 colores distintos, de una paleta de 262144 (2 ) colores, con
una resolucin de 320 x 200. La mayora de los juegos, demos, aplicaciones grficas y dems
historias se han programado as. Este modo es uno de los ms usados en la programacin grfica,
debido a su facilidad de uso y rapidez (el modo texto tambin se llama 3h).
Toda imagen en este modo est almacenada en la posicin 0xA000 (la memoria de video)
y que representa lo que se estamos viendo en la pantalla.
Y se acabaron los rollos. Ahora ya podemos meternos en el lo este de hacer dibujitos con
nuestro maravilloso C. Pero antes aviso que lo que yo diga en este captulo solo lo he probado en
mi Borland C++ v 4.02, en fin...
Primer programa: Para comenzar a aprender de grficos lo suyo es empezar a usar las funciones
que Borland C trae en la carpeta BGI. Hay que tener claro que los ficheros *.BGI son ficheros de
cdigo ejecutable que el programador puede usar para crear cualquier forma grfica.
Para que cualquier programa de grficos en C funcione como tiene que ser, hay que incluir
el archivo de cabecera de grficos: #include <graphics.h>.
Adems, tenemos que incluir la librera graphics.lib en el momento de la compilacin. Para
ello tendremos que construir nuestro programa como un proyecto (ver tema 14). Le exigiremos que
la plataforma sea en DOS (standard), y pincharemos en el cuadro de Standard Libraries la opcin
BGI, para incluir sus ficheros.
Las funciones que vamos a usar dependen del adaptador y del monitor que se este
utilizando. El controlador seleccionado se carga desde el disco durante la inicializacin de la
biblioteca de grficos llamado initgraph( );. Voy a explicarlo con el ejemplo de abajo.
Despus de poner las libreras que vamos a usar, dentro de main he colocado dos
variables de tipo entero que son estrictamente necesarias. Su nombre puede ser cualquiera.
-gdriver: contendr un nmero que se
refiere al controlador de video que
tenemos en el cacharro. Al usar
DETECT, detecta automticamente el
hardware presente en el sistema y
selecciona el modo de vdeo con la
resolucin adecuada. Los posibles
valores para el controlador son:
DETECT
CGA
MCGA
EGA64
EGAMONO
IBM8514
HERCMONO
ATT400
VGA
PC3270
#include <graphics.h>
#include <stdio.h>
#include <conio.h>
main( )
{
int gdriver = DETECT;
int gmodo;
0
1
2
4
5
6
7
8
9
10
Ahora llegamos a la chicha del asunto, el initgraph(int *controlador, int *modo, );. Como la
funcin, definida en <graphics.h>, maneja punteros que apuntan a enteros, cuando la llamemos en
nuestro programa debemos pasarle la direccin en que se encuentran esos enteros que necesita.
Como tercer parmetro de la funcin especificaremos entre comillas la ubicacin o path en
el que estn las funciones de BGI, en mi caso en la subcarpeta BGI de mi Borland C++. Si no
ponemos nada, buscar en el directorio actual (igual que con la fopen( );).
La funcin que uso, dibuja una lnea desde un punto hasta otro. Despus espero a que el
usuario pulse alguna tecla, y paso a la funcin que cierra el modo grfico, closegraph( ); (como la
fclose( );) que implica la devolucin al sistema de la memoria que se utilizaba para tener los
controladores y las fuentes grficas en uso. Si has ido siguiendo todos los pasos, te habr salido la
pantalla negra y una raya blanca transversal en medio. Como ya hemos cerrado el modo grfico,
deber aparecer una ventana de MS-DOS con los letreros que hay en las puts finales. Y ya est.
Detectar la tarjeta y el modo: para facilitar la manera de iniciar el modo grfico en un programa,
hay una funcin cuya sintaxis es la siguiente:
detectgraph (&tarjeta , &modo);
Detecta el tipo de tarjeta que tenemos instalado. Si en el primer argumento retorna -2
indica que no tenemos ninguna tarjeta grfica instalada (cosa bastante improbable).
Conocer el estado del modo grfico: se hara con la siguiente funcin.
graphresult( );
Retorna el estado del modo grfico. Si no se produce ningn error devuelve 0 (o bien el
valor predefinido grOK), de lo contrario devuelve un valor entre -1 y -16. Para ver el tipo de error
que se ha producido, tenemos otra funcin:
grapherrormsg( );
Esta funcin devuelve un mensaje de error (un string) asociado al valor que devolvi la
funcin anterior, es decir, la graphresult( );.
Dejar las cosas como estaban: se hace con la funcin de abajo, y se parece un poco al
closegraph( );. Reestablece el modo de video original (anterior a initgraph( );).
restorecrtmode( );
Y aqu un ejemplo resumiendo un poco
las funciones vistas antes. Declaramos tres
enteros: tarjeta, para el tipo de tarjeta que
tenemos; modo, para escoger el mejor; y error
para guardar un nmero de error determinado.
La funcin detectgraph se lleva las
direcciones de las dos primeras variables para
meterles los nmeros de tarjeta y modo que
vengan al caso.
#include <graphics.h>
#include <stdio.h>
#include <conio.h>
main( )
{
int tarjeta, modo, error;
detectgraph(&tarjeta, &modo);
if (error)
printf("%s", grapherrormsg(error) );
error = graphresult( );
else
{
getch( );
closegraph( );
}
}
Al ejecutarlo, si no hay problemas con tu tarjeta grfica, se pondr la pantalla negra hasta
que pulses una tecla, y si no, te dar un error.
BLACK
BLUE
GREEN
CYAN
RED
MAGENTA
BROWN
LIGHTGRAY
8
9
10
11
12
13
14
15
DARKGRAY
LIGHTBLUE
LIGHTGREEN
LIGHTCYAN
LIGHTRED
LIGHTMAGENTA
YELLOW
WHITE
setcolor (1);
setcolor (BLUE);
setbkcolor (4);
setbkcolor (RED);
putpixel ( x, y, color ): pone el pxel de coordenadas (x, y) del color que indiquemos como
tercer parmetro.
getpixel ( x, y ): devuelve el color que tiene el pxel de coordenadas (x, y).
line ( x, y, x, y ), dibuja una lnea desde (x1,y1) hasta (x2,y2)
usando el color actual.
Ejemplo: line(380, 395, 420, 345);
circle ( x, y, radio ): dibuja un crculo con el centro en la posicin (x, y),
y su radio ser el tercer parmetro.
Ejemplo: circle(429, 358, 30);
rectangle ( x, y, x, y ): dibuja un rectngulo desde (x, y) hasta (x, y).
Ejemplo: rectangle(50, 120, 580, 210);
arc (x , y, angulo1 , angulo2 , radio): Dibuja un arco cuyo centro est en x,y, de radio r, y que va
desde angulo1 a angulo2. Ej: arc(200,200,90,180,40);
sector(x, y, ngulo_inicial, ngulo_final ,radio_x, radio_y): dibuja y rellena con la trama en uso
un sector elptico con centro en las coordenadas (x,y) y los radios horizontal y vertical
indicados.
ellipse(x, y, ngulo_inicial, angulo_final, radio_x, radio_y): traza un sector elptico con centro en
las coordenadas (x, y) a partir de los valores dados en grados para un ngulo inicial y un
ngulo final, con los radios horizontal y vertical indicados.
setlinestyle (estilo, 1 , grosor): Selecciona el estilo de lnea a utilizar. El estilo puede tomar un
valor de la tabla de abajo. El grosor puede tomar dos valores: 1 = normal y 3 = ancho.
Ejemplo: setlinestyle(2,1,3);
SOLID_LINE
DOTTED_LINE
CENTER_LINE
DASHED_LINE
USERBIT_LINE
continua
lnea punteada
lnea con rayas finas
lnea con rayas pequeas
lnea con rayas tipo guin
0
1
2
3
4
setfillstyle (patron , color): Selecciona el patrn y el color de relleno. El patrn puede tomar un
valor de 0 a 12.
EMPTY_FILL
SOLID_FILL
LINE_FILL
LTSLASH_FILL
SLASH_FILL
BKSLASH_FILL
LTBKSLASH_FILL
HATCH_FILL
XHARCH_FILL
INTERLEAVE_FILL
WHIDE_DOT_FILL
CLOSE_DOT_FILL
color de fondo
color slido
color en lneas
lnea tipo /
lnea tipo /
lnea tipo \
lnea tipo \
cruzamiento de lneas
lneas X
cruzamiento tipo fino
malla de puntos
malla de puntos densa y fina
0
1
2
3
4
5
6
7
8
9
10
11
setfillstyle(9, 3);
bar(5, 5, 60, 60);
bar3d (x, y, x, y, profundidad, tapa): Dibuja una barra en 3d, son los mismos valores que bar
adems de la profundidad y la tapa: 0 si la queremos sin tapa y 1 si la queremos con tapa.
Ejemplo: bar3d(100,100,400,150,40,1);
pieslice (x , y , angulo1 , angulo2 , radio): Dibuja un sector. Hace lo mismo que arc, pero
adems lo cierra y lo rellena. Ejemplo: pieslice(250,140,270,320,50);
setviewport (x, y, x, y, tipo): Define una porcin de pantalla para trabajar con ella. La esquina
superior izquierda est determinada por x1,y1 y la inferior derecha por x2,y2. Para tipo
podemos indicar 1, en cuyo caso no mostrar la parte de un dibujo que sobrepase los lmites
del viewport, o distinto de 1, que s mostrar todo el dibujo aunque sobrepase los lmites. Al
activar un viewport, la esquina superior izquierda pasar a tener las coordenadas (0,0). Para
volver a trabajar con la pantalla completa, deberemos escribir: setviewport(0,0,639,479,1);.
Funciones de escritura: funciones de C para escribir en modo grfico (<graphics.h>):
settextstyle ( ): cambia la fuente para el tipo de letra que imprimamos en pantalla. Las fuentes
ms comunes son las que van de 0 a 4. La direccin puede ser: HORIZ_DIR (0) o
VERT_DIR (1). El tamao puede tomar de 1 a 10. En mi Borland C++ las fuentes son:
DEFAULT_FONT
TRIPLEX_FONT
SMALL_FONT
SANS_SERIF_FONT
GOTHIC_FONT
SCRIPT_FONT
SIMPLEX_FONT
TRIPLEX_SCR_FONT
COMPLEX_FONT
EUROPEAN_FONT
BOLD_FONT
Ejemplo: settextstyle(2, 0, 5);
0
1
2
3
4
5
6
7
8
9
10
outtext( texto ): Muestra una cadena de texto en la posicin actual (en los modos grficos no
existe un cursor visible, pero la posicin actual se conserva como si existiera uno invisible) y
con la fuente actual.
outtextxy (x, y, texto): escribe a partir de la posicin (x, y) de la pantalla el texto especificado,
con el color actual y la fuente actual.
settextjustify( justificacion_horizontal, justificacion_vertical ): Sirve para escribir de manera
justificada. La justificacin, tanto horizontal como vertical se realiza mediante los valores:
LEFT_TEXT, CENTER_TEXT, RIGHT_TEXT, BOTTOM_TEXT y TOP_TEXT
#include <conio.h>
#include <graphics.h>
#include <stdio.h>
#include <stdlib.h>
main( )
{
int contador, tarjeta, modo;
detectgraph(&tarjeta, &modo);
initgraph(&tarjeta, &modo, "C:\\BC4\\BGI");
for(contador = 0; contador <= 15; contador++)
{
setbkcolor(contador);
printf("Numero %i\n", contador);
getch( );
}
}
#include<graphics.h>
#include<conio.h>
#include<dos.h>
main( )
{
int driver,mode;
detectgraph(&driver, &mode);
initgraph(&driver,&mode,"C:\\BC4\\BGI");
settextstyle(4,0,5);
outtextxy(220,200,"MARTIN");
rectangle(100,100,530,370);
sleep(2);
closegraph( );
}
#include<graphics.h>
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<math.h>
main( )
{ int controlador,modo;
detectgraph(&controlador, &modo);
initgraph(&controlador,&modo,"C:\\BC4\\BGI");
int i;
setcolor(RED);
setbkcolor(WHITE);
line(0,275,639,275);
line(300,0,300,479);
rectangle(1,1,638,478);
gotoxy(10,5);
printf("Grafica de (sen x)/x");
for(i=1;i<=639;i+=1)
{
putpixel(i,275-200.0*sin((i-300.0)/10.0)/((i-300.000061)/10),BLUE);
}
getch( );
closegraph( );
}
Ejemplo de setviewport
Iniciar modo grfico, bla,
bla, bla...
Elegimos el color con una
funcin que no haba explicado
antes, getmaxcolor( ), que devuelve
el nmero de color ms alto de que
disponemos.
#include <stdio.h>
#include <conio.h>
main( )
{
int gdriver, gmode;
detectgraph(&gdriver, &gmode);
initgraph(&gdriver, &gmode, "c:\\BC4\\BGI");
setcolor(getmaxcolor( ));
Despus, escribimos un
mensaje en el modo de pantalla
completa por defecto (todo lo
mximo).
Luego elegimos nuestra
porcin de pantalla y sacamos otro
mensaje con la pantalla nueva, sin
limpiarla para que se vea bien la
diferencia.
Incluir los fichero de la BGI en un proyecto: aqu explico cmo crear aplicaciones grficas sin
necesidad de incluir el archivo EGAVGA.BGI y los archivos de fuentes de texto (*.CHR) junto con la
aplicacin.
En primer lugar hay que convertir el archivo EGAVGA.BGI en un archivo objeto (*.OBJ). Lo
haremos con la utilidad BGIOBJ.EXE que se encuentra en el directorio BGI, de manera que al
ejecutarlo hay que pasarle como argumentos:
BGIOBJ EGAVGA
Si se ha creado correctamente, debera indicarnos el nombre pblico del driver,
EGAVGA_driver.
Supongamos que nuestra aplicacin utilizar la fuente TRIPLEX, as que tambin la
convertiremos pasando los argumentos:
BGIOBJ TRIP
El nombre pblico de la fuente ser
triplex_font. Luego, una vez que tengamos la
ventana de nuestro proyecto abierta, deberemos
aadir los archivos que necesitamos. Esto se
hace con la opcin Add node. Habr que aadir
todos los fuentes de la aplicacin, y los archivos
EGAVGA.OBJ y TRIP.OBJ que hemos creado
anteriormente.
#include <conio.h>
#include <graphics.h>
main( )
{
int tarjeta, modo, error;
registerbgidriver(EGAVGA_driver);
registerbgifont(triplex_font);
detectgraph(&tarjeta, &modo);
initgraph(&tarjeta, &modo, "");
/* dibujamos un crculo relleno */
fillellipse(200,200,20,20);
registerbgidriver(EGAVGA_driver);
registerbgifont(triplex_font);
getch( );
Debera quedarnos parecido al ejemplo.
Desventajas de usar los BGI para grficos: Para empezar, al incluir la librera graphics.lib, el
tamao del programa ejecutable aumenta sobre los 18.000 bytes debido a la inclusin de todas las
funciones, aunque slo usemos algunas de ellas.
Adems, el programa al ser ejecutado deber tener disponible en el directorio actual (u otro
establecido mediante initgraph) el fichero BGI correspondiente al modo que se inicialice, y aunque
se puede linkar junto al ejecutable usando la utilidad BGIOBJ.EXE, el sistema BGI sigue pecando
en el sentido que ms puede quejarse un programa: su velocidad.
Si un programador desarrolla una librera para, por ejemplo, 320 x 200 x 256 colores,
crear las funciones especficas para ese modo, aprovechando las caractersticas que la tarjeta
nos brinde para l, y obteniendo una "librera especializada" que funcionar exclusivamente en
dicho modo. Por contra, los BGI tienen que funcionar para distintos modos de video, y todo eso
dentro de la misma funcin detectando en cada momento la resolucin que se est usando y de
una manera "estndar" para todos ellos, por lo que el cdigo ejecutable resultante ser demasiado
lento para intentar hacer con l un programa de carcter profesional.
Esto no quiere decir que no sea recomendable el aprendizaje del sistema BGI, sino que
debe ser tomado como base porque es la mejor manera de iniciarse en los modos grficos.
if (!estado.x.ax)
{
printf ("\n ERROR: no hay ratn.") ;
exit (1) ;
}
/* Si no hay raton... */
estado.x.ax = 1;
int86 (0x33, &estado, &estado) ;
/* Muestra el ratn */
Fin