You are on page 1of 149

Martn

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.

Gestin dinmica de la memoria.

10

Ficheros (usar el Disco Duro).

11

Algoritmos establecidos.

12

Ms sobre C.

13

Interrupciones (usar los Perifricos).

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( )
{

printf("Yo kiero hazer ya un programa.");

Siempre que el cdigo fuente original


sufra modificaciones, se crear un *.bak, o
copia de seguridad para la opcin deshacer de
las opciones Edicin de C++. Ahora vamos a
ver por encima como se escribe, es decir, a
hacer nuestro PRIMER PROGRAMA.
Por ejemplo, quieres que imprima en
pantalla: Yo quiero hacer ya un programa.
Pues bien, lo primero es poner un
comentario para saber de lo que va el
programilla. Para ponerlos, empezamos con /* y
lo cerramos con */. Esto lo ignora el lenguaje a
la hora de compilar, linkar, o ejecutar el
programa. En el caso de C++ tambin podemos
usar // para comentarios de una sola lnea:
// Esto es un comentario de una sola lnea.

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

A lo largo de un programa iremos necesitando unas variables (espacio de memoria al que


nosotros asignamos un nombre y en el que podemos almacenar datos). Si por ejemplo declaramos
una variable char, el programa guardar o reservar en la memoria RAM un espacio para luego
introducir en l un carcter. Lo de declarar se hace para meter valores en las funciones. Ejemplo:
char un_caracter = a donde un_caracter es el identificador; luego, si usamos la variable
un_caracter en una printf por ejemplo, sabr lo que vale esa variable (vale a).
Los tipos de variable ms habituales son:
-char :
-unsigned char :

un carcter ASCII (una letra, una cifra, un signo de puntuacin,


etc). Se indica entre comillas simples. Desde 128 a 127.
carcter sin signo. Desde 0 a 255.

-int :
-short :
-long :
-unsigned :
-unsigned short :
-unsigned long :

nmero entero, desde 32.768 a 32.767.


nmero entero corto, igual que int.
nmero entero largo, desde 2.147.483.648 a 2.147.483.647.
entero sin signo. Desde 0 a 65.535.
entero corto sin signo. Desde 0 a 65.535.
entero largo sin signo. Desde 0 a 4.294.967.295.

-float :
-double :
-long double:

nmero real con decimales, 34 por 10 elevado a +/-38 (7 dgitos).


real doble, 17 por 10 elevado a +/-308 (15 dgitos).
real doble largo, 17 por 10 elevado a +/-4932 (15 dgitos).

-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;

/* Esto declara un carcter. */


/* Esto declara un entero. */
/* Esto declara un real. */

un_caracter = 'a';
un_entero = 15;

/*Esto guarda una a */


/*Esto guarda un 15 */

un_real = 27.62;

/*Esto guarda un 2762 */

printf("%c es el caracter.\n", un_caracter);


printf("%d es el entero.\n", un_entero);
printf("%f es el real.", un_real);
}

Una vez declaradas las variables,


ponemos despus lo que queremos que
contenga
cada
una:
un_caracter
contendr en RAM el valor a, la variable
un_entero, 15, y por ltimo la variable
un_real, 27,62.
De esta manera, cuando ejecute
las printf sabr donde buscar los valores
que necesite. En los moldes %c, %d y %f
escribir los valores que se piden.

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;

Las variables pueden tambin ser inicializadas en el momento de definirse:


int multiplicador = 1000, multiplicando = 2, resultado;
Vamos a ver un ejemplo:
#include <stdio.h>
main( )
{
int multiplicador = 1000 , multiplicando = 2 ;
printf("Resultado = %d\n", multiplicando * multiplicador);
return;
}

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;

La variable masa declarada el algn punto del programa es convertida


a tipo int, y la constante 1.7 (que es de tipo float) tambin.

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));
...

Ms variables: Aqu se explican varias formas de clasificar los datos.


-const: (igual que #define) para que
un valor no cambie durante la
ejecucin del programa (constante).
Si intentas modificar el valor, el
compilador te da un aviso cuando
quieras sacar el ejecutable (Cannot
modify a const object in function
main). Si en el ejemplo detrs de la
printf pusiramos DOS_PI = 3.14; no
nos dejara compilarlo.

#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.

int i=1, j, k; //se declaran antes de main( )

Tambin estas variables pueden ocultarse


mediante la declaracin de otra variable con el mismo
nombre en el interior de un bloque. La variables
extern permiten transmitir valores entre distintas
funciones, pero sta es una prctica considerada
como peligrosa. A continuacin se presenta un
ejemplo de uso de variables extern.

// j, k son visibles

main( )
{
int i=3;
int func1(int, int);
...
}

// i=1 se hace invisible

int func1(int i, int m)


{
int k=3;
// k=0 se hace invisible
...
// i=1 es invisible
}

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 

real suma = 5.7;

typedef letra char;

luego 

letra mi_caracter;

Operadores

Operadores aritmticos: permiten realizar operaciones matemticas en lenguaje C. Existen dos


tipos de operadores aritmticos:

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.

++
--

Incremento (suma 1). Es decir: a++ es igual que a = a + 1.


Decremento (resta 1). Es decir: a-- es igual que a = a 1.
Cambio de signo.

+
*
/

Nota: para hacer el incremento o decremento de una variable


antes de operar con ella, hay que colocar delante de ella y no
detrs el ++ o -- segn quieras aumentar o disminuir en 1.
Ejemplo: la primera printf pondr x = 3 y luego la incrementar
en 1, por lo que la segunda pondr x = 5 (++x).

int x = 3;
printf(%d, x)
printf(x = %d, x++);
printf(x = %d, ++x);

-Formas reducidas de poner frmulas: en C el signo = significa machacar el valor de la


variable por lo que haya despus.
a = a + b;
es lo mismo que
a+=b;
a = a - b;
es lo mismo que
a-=b;
a = a * b;
es lo mismo que
a*=b;
a = a / b;
es lo mismo que
a/=b;
a = a % b;
es lo mismo que
a % = b;
Operadores entre bits: podemos hacer operaciones entre bits de dos nmeros (producto, suma,
suma exclusiva, etc), que se indican aqu abajo. Para hacer operaciones de este tipo con
nmeros, stos deben estar en binario para comparar los unos y ceros que los componen.
Operador

Operacin

Con Bits

Complemento (opuesto), cambiar 0 por 1 y viceversa. ~1 = 0


El complemento de 2 (10 en binario) es 1 (01).
~0 = 1

&

Producto lgico (and), se cumplan los dos.

0&0=0
0&1=0
1&0=0
1&1=1

Suma lgica (or), se cumplan alguno.

0|0=0
0|1=1
1|0=1
1|1=1

Suma exclusiva (xor), solo puede cumplirse uno.

<<
>>

Desplazamiento hacia la izquierda


Desplazamiento a la derecha

0
0
1
1

^0=0
^1=1
^0=1
^1=0

Ejemplo 1: si nos piden el resultado de D3^8E, lo primero es pasar


estos nmeros hexadecimales a base binaria. Se nos quedan
11010011 ^ 10001110, de lo que obtenemos el nmero 01011101 (5D).

11010011
^ 10001110
01011101

Ejemplo 2: si ponemos 05 << 4, lo que hace es desplazar el valor 00000101 (5 en binario)


4 unidades a la izquierda, de manera que nos quede 01010000.
Ejemplo 3: Una aplicacin que tienen los operadores
de desplazamiento de bits es para realizar
multiplicaciones y divisiones rpidas con enteros.
Como se ve en la siguiente tabla, donde un
desplazamiento a la izquierda es multiplicar por 2 y
uno a la derecha dividir por 2.
Los desplazamientos son mucho ms rpidos
que la multiplicacin (*) o la divisin (/) por dos. Por lo
tanto, si se quieren multiplicaciones o divisiones
rpidas por 2 usa desplazamientos.

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

Operadores relacionales: se utilizan para comparar el contenido de dos variables. En C existen


seis operadores relacionales bsicos:
Mayor que
Menor que
Mayor o igual que
Menor o igual que
Igual que
Distinto que

>
<
>=
<=
==
!=

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

if ((nmero<10) && (nmero>1)) ...


if ((nmero==1) || (nmero=2)) ...

.
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:

(8 >= 5) && (!(5 <= 2))

Lo primero que se hace es evaluar la primera expresin. Como es verdadera, pero


con el operador AND no sabemos lo que pasa hasta comprobar la segunda expresin
(aunque la primera sea 1 si la segunda vale 0 se da por mala), pues la evaluamos.
La segunda en principio no es verdadera, pero por el hecho de ponerle la ! delante,
se da por buena, ya que es negar una negacin (no es no es verdadera), por lo que es
verdadera (un folln, no?). Al final tendramos (1) && (1), que es (1).

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.

Lista de Secuencias de escape: Ciertos caracteres no representados grficamente se


pueden representar mediante lo que se conoce como secuencia de escape. A continuacin
vemos una lista de las ms significativas:
\n
\r
\b
\t
\v
\\
\a
\f
\'
\"
\0
\ddd
\xddd

salto de lnea. ASCII = 179.


retorno de carro.
retroceso.
tabulacin horizontal.
tabulacin vertical.
contrabarra (slash).
alerta (un pitido).
salto de pgina.
apstrofe.
comillas dobles.
fin de una cadena de caracteres.
constante octal (mximo tres dgitos)
constante hexadecimal (dem).

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");

-Cerrada: consiste en obligar al programa a decidirse por dos o ms alternativas a la hora


de imponerle una condicin. La sintaxis de esto sera: if (condicin) sentencia1 else
sentencia2, lo que significa que si se cumple la condicin haz tal, pero si no (else) haz la
otra. Tanto en la if como en la else, si las sentencias pasan de una, se deben meter entre
llaves {}. Por otra parte, si lo que queremos es que compruebe dos condiciones antes del
else final, lo que hacemos es poner: if (condicin) sentencia1 else if (otra condicin)
sentencia2 else sentencia3, es decir, si es blanco, haz algo, pero si es negro, haz otra
cosa. Que tampoco, pues haz la ltima cosa. Esto se llama sentencia encadenada.
Ejemplo de sentencia encadenada:

if ... else normal

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);
}

-Modelo 2: (bucle anidado) empieza con t = 0, y


como es menor que 100, hacemos las rdenes de
dentro de las llaves, dando un 1 a count y
comenzando otro bucle, que al no tener nada
dentro del parntesis acaba con el break. Escribe
en pantalla un 1, aumenta count en 1 y dice que
cuando count llegue a 10, rompa, de manera que
sale de la llave y termina un ciclo de la for inicial.
Ya tiene puesto en pantalla: 123456789, e ir
ponindolo una y otra vez hasta que t iguale o
supere el valor 100. Lo que hace este programa es
escribir cien veces en pantalla los nmeros del 1 al
9.

int t, count;
for(t=0; t<100; ++t)
{
count = 1;
for(;;)
{
printf("%i", count);
count++;
if(count == 10)
break;
}
}

-Modelo 3: mrate el getchar ms abajo. Dentro de


la for no ponemos nada, con lo que simplemente
tenemos un bucle del que saldremos (break)
cuando se cumpla la condicin que impone la if:
que la variable ch (que pide coger uno a uno
todos los caracteres que escriba el usuario,
machacando el que haya guardado por el
siguiente cada vez) sea igual al carcter A. Si el
usuario escribe: k fj heligfAlej glj y pulsa
<Intro> dir Tecleo una A.
-Curiosidad 1: si
quisieras escribir las tablas
de multiplicar del uno al
cinco, mira que fcil sale
metiendo una for en otra
(anidada).
-Curiosidad 2: y si quisieras
imprimir todas las letras de
la a a la z, observa de qu
manera tan simple se hace.

char ch;
for(;;)
{
ch = getchar();
if(ch =='A')
break;
}
printf("Tecleo una A");

int tabla, numero;


for (tabla=1; tabla<=5; tabla++)
for (numero=1; numero<=10; numero++)
printf("%d por %d es %d\n", tabla, numero, tabla*numero);

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.");

En el ejemplo, el programa acta de la


siguiente forma: inicializa la variable entera x con el
valor 1, y pide que mientras x sea menor o igual
que 5, escriba en pantalla su valor y despus lo
incremente en uno. Escribir por tanto los valores
1, 2, 3, 4, 5 y por ltimo, cuando x ya ha llegado a
valer 6, pondr Fin del bucle. Si de buenas a
primeras, la x valiese 6 o ms, directamente
pondra lo de 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);

getche: captura un carcter produciendo eco en la pantalla y sin pulsar <Intro>.


#include <conio.h>

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.

clreol: Borra desde la posicin del cursor hasta el final de la linea.


#include <conio.h>
wherex: Retorna la columna en la que se encuentra el cursor. Necesita de una variable entera
para recoger el nmero que devuelva ( Ejemplo: col = wherex( ); ).
#include <conio.h>
wherey: Retorna la fila en la que se encuentra el cursor.
#include <conio.h>

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;

atof ( ): (ASCII to float) cambia el carcter a


entero real.

printf("Mete un entero con signo => ");


gets(cadena);
numero = atoi(cadena);
printf("Un %d. Mola. Ale.", numero);

atol ( ): (ASCII to long) cambia el carcter a


entero largo.

Conversin de tipo de letra: para pasar de maysculas a minsculas y viceversa.


toupper ( ): convierte un carcter minscula en mayscula.
tolower ( ): pasa un carcter mayscula a uno en minscula.

kbhit: Espera la pulsacin de una tecla


para continuar la ejecucin. Sintaxis:
#include <conio.h>

...
while (!kbhit( )); /* Mientras no pulsemos una tecla... */
...

Funciones matemticas:
abs ( ): valor absoluto.
#include <stdlib.h>

int numero = -1234;


printf("El numero %d en valor absoluto es %d", numero, abs(numero));

sin ( ): devuelve el seno de un valor. Trabaja con


ngulos en radianes y tiene una versin para enteros
largos, sinl ( );.
#include <math.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);

log10 ( ): logaritmo en base diez. Igual que log.


pow ( ): potencia.
float x = 2.0, y = 3.0;
printf("%f elevado a %f es %lf", x, y, pow(x, y));
sqrt ( ): raiz cuadrada.
float x = 4.0, result;
result = sqrt(x);
printf("La raiz cuadrada de %f es %f", x, result);

acos (double x): Calcula el arco coseno de x.


asin (double x): Calcula el arco seno de x.
atan (double x): Devuelve el arco tangente en radianes.

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);
}

En este programa hemos utilizado lo siguiente:


filas: numero de filas.
colum: numero de columnas.
resp1: para un bit.
resp2: para un byte.
resp3: para dos bytes.

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);
}

En la include matriz.h estarn incudas:


#include <matriz.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

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);

Un trmino que hay


que saber es el de pila en
int potencia(int exp);
tiempo de ejecucin, que
es un sistemilla que pone
main( )
main( )
C para casos de muchas
{ printf("%d",potencia(8));
}
{ puts("Esto es main( )");
funciones (ejemplo de
Primera( );
}
indirecta). Consiste en ir
int potencia(int exp)
guardando en la memoria
{
void Primera( )
el
camino
que
va
if (exp == 0)
{ puts("Esta es Primera( )");
siguiendo
el programa
return (1);
Segunda( );
}
mientras va de una
else
funcin a otra y luego a
return(2 * potencia(exp - 1));
void Segunda( )
otra, para luego poder
}
{ puts("Esta es segunda( )"); }
volver al lugar del que
sali (el main).
En el ejemplo de recursividad directa podemos perdernos (yo me he perdido...). En el main
pide un valor entero, que se lo deber entregar la funcin potencia utilizando de primeras el valor 8.
Ahora viene lo bueno.
Pregunta si la variable exp es igual a 0, pero como es 8, se va al else que devuelve el
resultado de multiplicar 2 por lo que salga de volver a usar la funcin potencia con el valor de exp
restndole 1. Se vuelve al principio de la funcin potencia, ahora usando el valor 7, que como no
es 0, hace ora vez lo del else y en la operacin vuelva a pedir otro resultado de la funcin, pero
ahora el valor a utilizar es 6, y seguir en ese bucle hasta que el valor sea igual a 0, en cuyo caso
har primero la operacin de 2 por 1 (ya que devuelve un 1), el resultado lo multiplicar por 2 otra
vez, as lo har tantas veces como vueltas haya dado por la funcin potencia. Es como si
empezsemos a hacer un nudo y cuando terminramos, lo deshicisemos. Al final lo que hace es
dos por uno, el resultado por dos, el resultado por dos, el resultado por dos, as hasta ocho veces,
o lo que es lo mismo, elevar dos a ocho. Qu bien me explico!
En el ejemplo de recursividad indirecta primero imprime desde main el letrero Esto es
main( ), y llama a Primera( ). En la pila se guarda este paso para luego volver. En Primera( )
imprime la frase Esto es Primera( ) y llama a la funcin Segunda( ), metiendo en su pila este paso
y yndose a la funcin, imprime Esto es Segunda( ), y termina con la }, por lo que recurre a la pila
para ver a donde debe ir, y lo primero que se encuentra es que tiene que ir a Primera( ). Se va y se
sita en el lugar exacto del que sali (despus de la puts), y continua con las instrucciones, pero
solo queda hacer } por lo que vuelve a preguntar a la pila a donde va, y le dice que a main, vuelve
y como siempre se pone en el lugar del que sali (la puts) y contina haciendo instrucciones del
main, que como no hay ms que } termina nuestro programa.
La recursividad es una tcnica de programacin muy potente que requiere habilidad y
paciencia para implementarse correctamente.

Punteros

Introduccin: Como usa C la Memoria


Cuando hablamos de memoria nos estamos refiriendo a la memoria RAM del ordenador.
Son unas pastillas que se conectan a la placa base y nada tienen que ver con el disco duro. Todas
las cosas que hagamos en C irn ocupando espacio en la memoria RAM, cuyas posiciones estn
identificadas por una direccin, empezando por la 0. Si por ejemplo en uno de nuestros programas
declaramos una variable char, C guardar o reservar en la memoria un espacio para luego
introducir en l un carcter.
Pues bien, lo que hace C en la RAM es empezar por una direccin (por ejemplo 3AF4) en
la que no haya nada de importancia guardado (a lo mejor hay un %, un # o una alpargata vieja,
basura). A partir de ah empezar a escribir lo que le mandemos. A esto se le llama registro base.
Ahora nos da por meter ese char, pues la
RAM guarda un byte para m, de forma que ahora
pone una direccin relativa, en la que se suma con el
signo : el registro base y el desplazamiento que hace
(para saber la siguiente direccin a usar).
3AF40
+ 0001
3AF41

3AF4:0001 

El nmero que obtenemos es lo que se llama direccin absoluta. Es donde guardar el


carcter que luego queramos introducirle cuando ejecutemos nuestro programa.
El ordenador trabaja con direcciones en binario, pero siempre nos las muestra en base
hexadecimal para facilitarnos la tarea de encontrarlas o identificarlas.
Las variables que sean de tipo char ocupan siempre un byte (ocho bits), por lo que los
valores que se pueden almacenar oscilan desde el 00000000 al 11111111en binario, o lo que es lo
mismo en decimal, del 0 al 255 (osea, los caracteres del cdigo ASCII, cada uno con su nmero).
Nota: Si no te crees esto, mira la funcin sizeof en el tema de funciones.
Pongamos un ejercicio en C++ para ver de que va esto en realidad.
#include <stdio.h>
#include <math.h>
#define poten(x, y) pow(x, y)
/* Este programa calcula el resultado de elevar un numero por otro. El usuario debe introducir ambos
numeros.
Programador: Martin Lopez Sanchez. */
main( )
{
int a, b, resultado;
puts("Calcular el resultado de elevar un numero entero a otro.");
printf("Introduce a --> ");
scanf("%i", &a);
printf("Introduce b --> ");
scanf("%i", &b);
printf("\n");
resultado = poten(a, b);
printf("El resultado de %i elevado a %i es %i", a, b, resultado);
}

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 = &num;
para guardar la de
printf("la direccion del numero es %i\n", p);
num (p = &num;) 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;
}

/* Se va a la direccin que contiene puntero y vuelca sobre x un 11 */


/* Puntero mas un elemento, avanzando dos bytes por cada int
*/
/* Se va a la direccin que contiene puntero y vuelca en x un 22
*/
/* Igual que el anterior, pero sumndole uno, y pone en x un 23
*/
/* Aumenta puntero en un elemento y el * mete el contenido 33 en x */
/* Puntero se machaca por puntero + 1, metiendo un 33 en x
*/
/* El contenido de puntero se le suma 1 y lo mete en x, 34
*/

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.

cogerle gusto a esto de los punteros, que tan


#include <stdio.h>
#define PUNTO '.'
void cuentaletras (int *min, int *may, int *dig)
{
char C;
while((C =getchar( )) != PUNTO)
{
if(C>='a' && C<='z')
*min += 1;
if(C>='A' && C<='Z')
*may += 1;
if(C>='0' && C<='9')
*dig += 1;
}
}
main( )
{
int m, M, D;
m = M = D = 0;
printf("Voy a ver que escribes");
printf("punto para terminar)\n");
cuentaletras(&m, &M, &D);
printf("\Minusculas = %i\n", m);
printf("Mayusculas = %i\n", M);
printf("Digitos = %i\n", D);
}

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[ ]);

int fun_1(char *s);

Solucin: S, ya que char s[ ] es igual que char *s.


Escrituras chungas de punteros: aqu me he copiado una lista de traducciones.
int
int
int
int

*punt;
*punt [10];
(*punt) [10];
*punt (void);

int punt (char *a);


int *punt (char *a);
int (*punt) (char *a);
int (*punt (char *a)) [10];
int punt(char (*a)[ ] );

int punt (char *a[ ] );

int *punt (char a[ ] );


int *punt (char (*a)[ ] );
int *punt (char *a[ ] );

int (*punt)(char (*a)[ ] );


int *(*punt)(char (*a)[ ] );

int *(*punt)(char *a[ ] );

int (*punt [10] )(void);


int (*punt [10] )(char * a);

int *(*punt [10] )(char a);

punt es un puntero que apunta a un entero.


punt es un array de diez punteros que apuntan a enteros.
punt es un puntero que apunta a un array de 10 enteros.
punt es una funcin que no recibe valores, y devuelve un puntero
que apunta a un entero.
punt es una funcin que recibe un puntero que apunta a un
carcter, y al final devuelve un entero.
como la anterior, pero devuelve un puntero que apunta a un entero.
punt es un puntero que apunta a una funcin que recibe un puntero
que apunta a un carcter, y devuelve un puntero a entero.
punt es una funcin que recibe un puntero que apunta a carcter,
devolviendo un puntero que apunta a un array de 10 enteros.
punt es un puntero que apunta a una funcin que recibe un puntero
que apunta a un array de caracteres, y devuelve un puntero que
apunta a un entero.
punt es un puntero que apunta a una funcin que recibe un array
de punteros que apuntan a caracteres, y devuelve un puntero que
apunta a un entero.
punt es una funcin que recibe un array de caracteres, y devuelve
un puntero que apunta a un entero.
punt es una funcin que recibe un puntero que apunta a un array
de caracteres, y devuelve un puntero que apunta a un entero.
punt es una funcin que recibe un puntero que apunta a un array
de punteros a caracteres, y devuelve un puntero que apunta a un
entero.
punt es una funcin que recibe un puntero que apunta a un array
de caracteres, y devuelve un puntero que apunta a un entero.
punt es un puntero que apunta a una funcin que recibe un puntero
que apunta a un array de punteros que apuntan a caracteres, y
devuelve un puntero que apunta a un entero.
punt es un puntero que apunta a una funcin que recibe un array
de punteros que apuntan a caracteres, y devuelve un puntero que
apunta a un entero.
punt es una array de 10 punteros que apuntan a funciones, y cada
funcin devuelve un entero.
punt es una array de 10 punteros que apuntan a funciones. Cada
funcin recibe un puntero que apunta a un carcter y devuelve un
entero.
punt es una array de 10 punteros que apuntan a funciones. Cada
funcin recibe un carcter, y devuelve un puntero que apunta a un
entero.

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

Una cadena (arreglo en latinoamrica) es un conjunto de elementos del mismo tipo.


Existen dos tipos de cadenas: de nmeros de cualquier tipo, llamadas arrays, y de caracteres,
llamadas strings.
Ejemplo: Si yo ahora declaro una variable entera int numero; en memoria se guarda un espacio
para que yo pueda meter un numero. Pues yo lo que quiero hacer ahora es reservarme en la
memoria un espacio para guardar un total de 80 numero enteros de una sola vez.
Y esto lo conseguimos
haciendo la siguiente declaracin:
int numero [80]; con lo cual
reservamos en memoria ochenta
espacios seguidos (de ah lo de
cadena) numerados a partir del 0
para meter ochenta enteros. Cada
entero se guardar en una posicin
de la cadena, es decir, si queremos
meter un 9 en el quinto espacio,
escribimos tras la declaracin
numero [4] = 9; ya que empieza a
numerar por el 0.
La cantidad mxima de valores o caracteres que pueden guardar est slo limitada por la
cantidad de memoria disponible.
La cosa es ms gorda de lo que parece, por lo cual tras la introduccin, he escrito (y mi
trabajo me han costado) dos temas para tratar las cadenas a fondo.

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);

Si le asignas un nombre directamente puedes hacerlo de la manera soba (1) o de la


manera chachi (2) que tiene C para hacerlo.
1
2

char nombre[50] = {'N', 'e', 'o};


char nombre[50] = "Neo";

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.

char cad[ ]="El valor de";


int a=25986;
long int b=1976524;
float c=9.57645;
printf("%s a es %d\n",cad, a);
printf("%s b es %ld\n",cad, b);
printf("%s c es %f",cad, c);

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:

Solo hay un misterio en


este ejemplo: como se empieza
a contar desde el 0, hay que
restarle 1 a las posiciones para
que funcione bien.

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);

Declaramos una cadena llamada cad de


hasta 79 caracteres y el freno \0. La funcin
gets guarda el texto en la variable entre
parntesis en el momento en que el usuario
pulse la tecla <Intro> y luego la imprime en
pantalla cuando la puts se lo pide dentro de su
parntesis.

Funciones de manejo de strings (#include <string.h>): Aqu presento un conjunto de funciones


para trabajar con una o varias cadenas de caracteres. Todas deben llevar el argumento detrs.
strcpy ( ): copia a un espacio reservado para hacerlo, una cadena de caracteres (string copy).
Como para copiar el valor de una cadena de texto en otra, no podemos igualarlas (cad1 = cad2;)
ya que estaramos igualando dos punteros, debemos decir strcpy (destino, origen);.
En el ejemplo declaramos un espacio con el nombre
de string (para no complicarnos) y un puntero que apunte al
comienzo de esa cadena. Despus con la funcin se meten
en la cadena los caracteres a los que apunta el puntero y los
sacamos en pantalla. En realidad solo se mete la primera
letra a, pero sigue metiendo hasta llegar al \0 del final.

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.

Aqu pongo un ejemplo:


creamos cuatro strings cada una
con una palabra, y luego
ponemos
tres
condiciones
utilizando el if, que, claro est, se
cumplirn con el objetivo de ver
como funciona la strcmp y que
valores devuelve en cada
comparacin que realizamos.
En estas comparaciones,
hay que tener en cuenta que la
strcmp (que significa algo as
como
string
compare)
es
sensible a las maysculas y las
minsculas, es decir que no
considera iguales por ejemplo las
cadenas que guapo soy y Que
guapo soy.

#include <stdio.h>
#include <string.h>
main( )
{

char cadena_a[ ] = "jajaja", cadena_b[ ] = "jeje",


cadena_c[ ] = "jajaja", cadena_d[ ] = "jaen";
if ((strcmp(cadena_a,cadena_c)) == 0)
printf("\"%s\" es igual que \"%s\".\n",cadena_a, cadena_c);
if ((strcmp(cadena_a,cadena_b)) < 0)
printf("\"%s\" esta antes que \"%s\".\n",cadena_a,cadena_b);
if ((strcmp(cadena_a,cadena_d)) > 0)
printf("\"%s\" esta despues que \"%s\".\n",cadena_a,cadena_d);
}

char cad1[ ] = lolailo, cad2 [ ] = lolailo;


if( ! strcmp(cad1, cad2))
puts(Iguales);
else
puts(Distintas);

Un problemilla que podemos tener es que


como esta funcin da un 0 cuando se cumple, si
se encuentra dentro de una if, 0 significa que no
se cumple y se la salta, de manera que habra
que corregir esto con el operador ! (Not) para
que convierta el 0 en otro valor distinto.

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");
}

Declaramos una cadena


con una serie de letras al azar, un
puntero y un carcter. Pedimos al
usuario un carcter para que el
programa lo busque.
En la variable puntero
mandamos
que
guarde
la
direccin del carcter en la
cadena, y en resultado, el
resultado de restar a la direccin
que tiene puntero la direccin del
primero de los elementos de la
cadena (no olvides nunca que
string y & string [0] significan la
misma cosa). La cosa quedara
(un suponer):
letra
*ptr
ptr
string






B
B
523F:21D6
523F:21E1

En la variable resultado se guardara un 11 (que sale de restar hexadecimalmente las dos


direcciones) al que sumamos 1 para salga bien. Piensa que si dijramos que buscase la q que
est la primera en la cadena, nos dira que El carcter q est el 0, y eso queda cutre.
strrchr ( ): igual que strchr( ), solo que devuelve la direccin en que est la ltima aparicin de un
carcter en una cadena.
strtok ( ): Busca dentro de s1 una subcadena
igual a s2 , si lo encuentra modifica a s1
reemplazando la subcadena por NULL,
devolviendo la direccin de s1.
Si se lo vuelve a invocar con NULL en
lugar en el lugar del parmetro s1, continua
buscando en el string original una nueva
aparicin de s2. Cuando no encuentra la
subcadena , retorna NULL.
Sirve para descomponer un string en
"palabras de una frase" cuando stas estn
separadas por un mismo carcter o
caracteres.

#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);
}

strpbrk ( ): busca la direccin en que se


encuentra un carcter de una cadena que se
repite en otra. Con un ejemplo se ve mejor:
pbrk(monitor, raton);
Devolvera la direccin en la que est la t
de monitor, ya que es la primera letra de la
palabra raton que se repite.

#include <stdio.h>
#include <string.h>
main()
{
char texto[80], *puntero;
printf("Pon un texto pa quitar las vocales:\n");
gets(texto);
puntero = texto;

Ms que nada se usa para saber si en


una cadena existe un carcter en concreto,
como puede verse en el programa que me he
negociado aqu a la derecha.
Tras guardar la direccin de la cadena
en el puntero, pedimos que mientras que no
se encuentre un freno, busque en la cadena
primero la a, luego la b, etc, y en caso de
encontrar alguna de las vocales, machacarla
por un espacio.

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);
}

itoa ( ): convierte un entero a una cadena de caracteres.


Mola por que tenemos que indicar a qu base
(binaria -2-, octal -8-, etc) queremos convertirlo. Las
bases pueden ser desde 2 hasta 36, incluidas. Si el
entero es negativo y la base es 10, el primer valor
de la cadena ser el signo negativo.
ultoa ( ): convierte un entero largo sin signo (unsigned
long) a una cadena de caracteres. Por lo dems es
igual a la anterior. Cuidado con el tamao de la
cadena, que si no es suficiente empezamos con
problemas!

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:

1 Guardamos un string situado en la direccin que est


guardada en el puntero insulto. Reservamos un
espacio de hasta 50 caracteres para que el usuario
guarde su nombre, y luego lo insultamos juntando en
la printf su nombre con el contenido de la direccin a
la que apunta el puntero insulto.

char *insulto = "es tonto!";


char nombre [51];
puts("Como te llamas?");
gets(nombre);
printf("%s %s", nombre, insulto);

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.

Dentro de main declaramos una


cadena palabra de 13 espacios y un
puntero, adems de un entero que nos
valdr de contador en el for que nos espera,
no sin antes asignar a p la direccin de la H
del primer texto.
Una vez que entra en el bucle, ir
guardando uno a uno los elementos que
componen TEXTO1 en la cadena hasta que
llegue al \0.
Despus a p le metemos la
direccin de la c del segundo texto y por
ltimo imprimimos en pantalla el contenido
de palabra seguido de lo que apunte p, es
decir, una c, pero seguir leyendo e
imprimiendo caracteres hasta llegar al \0
del final de TEXTO2.

#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,

15, 17, 19, 21},


22, 24, 26, 28},
33, 35, 37, 39},
42, 44, 46, 48},

};

Esto quedara almacenado en RAM de la forma de mi


dibujo. En andaluz significa que hemos hecho una matriz de
cuatro filas por cinco columnas (en total veinte elementos de
2 bytes cada uno, 40 bytes).
En el dibujo que he hecho un peln ms abajo, vemos
que por ejemplo tabla[1] representa la segunda lista de
nmeros de nuestra tabla.
Aprovechando que me siento bien, vamos a
profundizar. Con los arrays se sigue manteniendo la regla
tabla == &tabla[0].
Si empieza vamos a poner en la direccin BACA, y
luego pedimos tabla + 2; estamos pidiendo que aumente la
direccin en dos filas ms, es decir, que avanzamos 20
bytes y nos situamos en la direccin en la que se encuentra
el 31.

Arrays multidimensionales y punteros: Supongamos una declaracin como la siguiente:


int mat [5] [3] ;
El nombre de la matriz (mat) es un puntero al primer elemento de un vector de punteros
mat[ ] (por tanto, existe un vector de punteros que tiene tambin el mismo nombre que la matriz),
cuyos elementos contienen las direcciones del primer elemento de cada fila de la matriz.
El nombre mat es pues un puntero a puntero. El vector de punteros mat[ ] se crea
automticamente al crearse la matriz. As pues, mat es igual a &mat[0]; y mat[0] es &mat[0][0].
De la misma manera, mat[1] es &mat[1][0], mat[2] es &mat[2][0], etc. La direccin base es
&mat[0][0]. Ahora imaginemos que creamos un puntero que apunta a otro (es decir, la direccin
que contiene es la de otro puntero) haciendo:
int **p;
**p = mat;
Una vez hechas las anteriores declaraciones, se tendrn las siguientes formas de acceder
a los elementos de la matriz:
*p
**p
*(p+1)
**(p+1)
*(*(p+1)+1)

es el valor de
es
es el valor de
es
es

Paso de arrays entre funciones: Como en los


strings no nos hemos detenido mucho en esto,
lo hacemos ahora.
Para empezar, he aqu un ejemplo en
el cual en main( ) declaro un array de tres
nmeros enteros y llamo a una funcin a la cual
paso la cadena entera (o su direccin). Una vez
en la funcin, imprimo los elementos que
componen la cadena. Bien fcil.
Esto lo podramos haber hecho ms
fcil creando un puntero y asignndole la
primera direccin del vector diciendo:
puntero = mis_numeros;
De esta manera
siguientes igualdades:

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

mis_numeros [0] = *puntero


mis_numeros [1] = *(puntero + 1)
mis_numeros [2] = *(puntero + 2)
Es decir, que el contenido del primer elemento de nuestro vector es igual al contenido de la
direccin que tiene el puntero, y dems.

En el segundo ejemplo, lo que hace es


meter los valores del vector desde una funcin.
Uno puede pensar: Pues desde una
funcin no se puede modificar una variable de
main( ) as por que s, pero con los punteros si
se poda, no? Y de eso se trata:
A la funcin le pasamos la direccin de
vector a un puntero llamado v, y en esa
direccin metemos primero un 20, luego en el
siguiente elemento int metemos un 40 (como si
dijramos *(v + 1)) de manera que le
asignamos valores no a una cadena de main( ),
sino a una serie de direcciones de RAM con la
idea de imprimirlas luego desde main( ).
Con esto ya vamos entrando algo ms
sueltos en el mundillo este de los punteros,
pero necesitamos ms ejemplos.
Sumar los elementos de un array: se consigue
fcilmente utilizando la recursividad, en el tema
sexto.
Creamos un array de siete elementos,
y desde main pedimos imprimir un nmero
entero, que sale de usar la funcin suma_ole
pasndole un 6 (que ser el ndice mayor del
array, ya que empieza a contar por el 0.
Como indice vale 6, hace el else que
pide la suma del elemento array[6] (osea, un 2)
y el resultado de usar la funcin con un 5
(indice 1). Acaba de dar vueltas cuando
indice vale 0, con lo que primero devuelve el 1,
luego el 1 + 100, 101 + 50, 151 + 40, 191 + 5,
196 + 1 y por ltimo ese 197 + 2, con lo que el
main imprimir 199.

#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");
}

Una curiosidad de buffer del


teclado: en este ejemplo,
vemos perfectamente como
funciona el buffer del teclado.
Lo que hacemos en primer
lugar es colocarle un filtro a la
scanf para que de toda la
cadena
que el usuario
introduzca, pare de capturar
en cuanto que se encuentre
un carcter que no sea un
letra mayscula. Luego con
un getchar dentro de un
bucle, cogemos el resto de
caracteres que quedaron sin
capturar hasta que se tope
con el \0.

#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;
};

Lo que acabamos de crear es una especie de plantilla, para


poder usarla por ejemplo a varias personas de las que se quiera
guardar informacin. Para asignar este modelo a una variable en
concreto, haramos:
struct datos martin;

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;

scanf (%s, &martin.nombre);


Esto suponiendo que el programa me pide mi nombre para guardarlo. Tambin podemos
usar otra forma de nombrar una estructura, y es usando el typedef (definicin de tipo) vista en el
tema de variables. Lo que hacemos es nombrar un tipo de estructura, de manera que con ese
nombre nosotros luego podemos crear todas las estructuras que queramos del tipo nombre. Al
ejemplo, que se ve mejor:
Dicho eso en un programa, cada vez que nos refiramos a
mis_variables
estaremos hablando de la estructura de datos que
typedef struct
contiene
num
y
nombre. As podemos asignar ms fcilmente este
{
molde de estructura a todas la variables de estructura que
int edad;
queramos:
char nombre[10];
mis_variables datos_persona1 ;
}mis_variables;
mis_variables datos_persona2 ;
Con esto hemos creado dos variables del tipo de la estructura mis_variables. Recuerda
siempre que ahora mis_variables es como otro tipo de variable, como pueda serlo int, char, float,
etc.
Si queremos dar valores iniciales a
#include <stdio.h>
una variable, hay una ley, y es que solo se
pueden
asignar
valores
iniciales
en
typedef struct
estructuras globales o estticas (static). Se
{
har como en el ejemplo de la derecha.
char nombre[50];
En l definimos un tipo de variable
como datos_personales, que es una
estructura que contiene espacio para meter el
nombre, la edad y el peso de un individuo.
Dentro de main declaramos una
variable del tipo estructura (por lo que es ms
correcto llamarla variable de estructura)
esttica, para que no vare, y le damos los
valores iniciales ordenados y separados por
comas
(,)
para
que
se
guarden
correctamente.

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);

Se hace casi igual que con los strings


cuando le damos valores iniciales.
Las printf se encargan de verificar la
correcta inicializacin de nuestra estructura.

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);
}

Es decir, no es como declarar una variable


int numero;. Esto se soluciona usando la funcin
malloc( ), aunque de una manera algo compleja. Al
ejemplo.
Hacemos una estructura con el nombre, la
edad y el peso, representada por el tipo
datos_personales. En main creamos un puntero que
apunte al tipo datos_personales y con malloc( )
pedimos que nos busque una direccin donde quepa
el tipo de variable datos_personales y que adems lo
guarde en el puntero.
Ahora viene lo bueno: pedimos el nombre,
y con gets enviamos lo que escriba el usuario a
nombre. Y donde est nombre? Pues est el la zona
de memoria apuntada por el puntero. De ah lo de:
ptr -> nombre
El smbolo -> se usa para indicar que lo
que hay delante apunta a lo que hay detrs. Su uso es
muy propio de estructuras de datos.
El resto de datos se guardado de manera
similar, y luego para ver que todo ha salido bien,
sacamos por pantalla el contenido de las variables de
nuestra estructura con el mismo mtodo del puntero
que apunta a algo.

Funciones y Estructuras: vamos a hacer que


una funcin devuelva una estructura entera.
Para ello, lo primero es tener la estructura
como un tipo de variable, mi_estructura.
Empezando como siempre por main,
creamos una variable llamada un_colega, del
tipo mi_estructura. Sobre un_colega volcamos
lo que se obtenga de ejecutar la funcin
leer_datos.
Nos encontramos con que la funcin
leer datos es del tipo mi_estructura (para que
reserve el espacio que necesite la estructura), y
no recibe nada (void). Creamos otra estructura
llamada reg, y en ella guardamos los datos
necesarios. Lo ltimo que hace la funcin es
devolver la estructura creada por ella misma a
main.
En main quien recibe esa estructura no
poda ser sino un_colega. Ahora lo que hace es
llamar a la funcin encargada de sacar los
datos por pantalla, envindole previamente la
estructura mi_colega, que la recoge otra vez
una estructura que acaba de crear la funcin
con el nombre de reg.
Imprime los datos y se acab el
programa.

#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;

typedef registro matriz[3][2];

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);
}

Declaramos una estructura con una


variable.
Despus declaramos otra distinta,
pero con la salvedad de que dentro de
ella hemos declarado una variable del
tipo de la primera estructura.
Dentro de main( ) declaramos otra
variable del tipo de la segunda
estructura.
Y ahora viene lo importante: para
indicar que guarde un 54 en la
variable de la primera estructura, lo
volcamos en primero, contenida en
segundo, contenida en valores.

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));
...

calloc ( ): se diferencia de malloc bsicamente en que permite indicar el nmero de asignaciones


que queremos hacer. Su sintaxis es: calloc (n de elementos, tamao de cada elemento);
Por ejemplo, declaramos un puntero
que apunte a un float y que se llama mat.
Luego al puntero le metemos por
medio de la funcin calloc una direccin
en la que nos quepan un total de veinte
float seguidos.
Como siempre, si no hay sitio
devolver NULL. Si esto ocurre, provoco
la finalizacin del programa.

float *mat;
mat = (float *) calloc (20, sizeof(float));
if (mat = = NULL)
{
printf ("\nNo hay memoria");
exit(0);
}
...

Como se puede observar no resulta rentable la declaracin de una variable simple de


forma dinmica, en primer lugar por que aunque la variable slo se utilice en una pequea parte
del programa, compensa tener menos memoria (4 bytes para un float) que incluir todo el cdigo
de llamada a malloc y comprobacin de que la asignacin fue correcta (esto seguro que ocupa
ms de 4 bytes). En segundo lugar tenemos que trabajar con un puntero con lo cual el
programa ya aparece un poco ms engorroso.
Nota: bsicamente da lo mismo utilizar malloc y calloc para reservar memoria, ya que
podramos hacer:
mat = (float *) calloc (20, sizeof (float));
mat = (float *) malloc (20 * sizeof (float));
La diferencia fundamental es que, a la hora de definir matrices dinmicas calloc es mucho
ms clara y adems inicializa todos los elementos de la matriz a cero.
realloc ( ): reasigna una nueva direccin al puntero segn las necesidades.
En el ejemplo, al puntero str
le asignamos una direccin en la
que se pueden meter 15 bytes
seguidos y copiamos empezando
por ah una cadena.
Imprimimos el contenido y la
direccin y luego reasignamos al
puntero una nueva direccin
indicando primero el mismo
puntero y luego la cantidad de
bytes que necesitamos.

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

En principio esta es la nica forma de definir


nuestro tipo de datos. Con esta definicin una matriz
3 x 3 ocupar 1000 x 1000 floats al igual que una matriz
50 x 50.
Sin
embargo
podemos
asignar
memoria
dinmicamente a la matriz y reservar slo el tamao que
nos hace falta. La estructura sera sta otra.
El tipo MATRIZ ahora debe ser un puntero
puesto que tenemos que reservar memoria para la
estructura que contiene la matriz en s y las
dimensiones.

MATRIZ inic_matriz (int x,int y)


{
MATRIZ temp;
temp = (MATRIZ) malloc (sizeof(struct mat));
temp->ancho = x;
temp->alto = y;
temp->datos = (float *) malloc (sizeof(float)*x*y);
return temp;
}

typedef struct
{
float mat[1000][1000];
int ancho,alto;
} MATRIZ;

struct mat
{
float *datos;
int ancho,alto;
};
typedef struct mat *MATRIZ;

Una funcin secundaria, llamada


desde main (el cual le pasa los valores x
e y), que nos crease una matriz sera
algo como en el ejemplo de la izquierda.
Desde luego, como no te hayas mirado
bien el tema anterior, estamos haciendo
el tonto.
La funcin es
devuelve la variable
es de tipo MATRIZ
hemos grabado en
dinmica.

de tipo MATRIZ, y
temp, que tambin
y es la matriz que
memoria de forma

En nuestro programa principal, para utilizar la matriz


declararamos algo as como lo que hay en el ejemplo de
la derecha.
Dnde borrar_matriz(a) libera la memoria reservada
en inic_matriz, teniendo en cuenta que se realizaron dos
asignaciones, una para la estructura mat y otra para la
matriz en s.

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( )
{

typedef struct mat MATRIZ;


MATRIZ a;
inic_matriz (&a,3,3);
.....
borrar_matriz (&a);

void inic_matriz (MATRIZ *p,int x,int y)


{
p->ancho = x;
p->alto = y;
p->datos = (float *)malloc(sizeof(float)*x*y);
}

Con este esquema el acceso a la matriz sera *(a.datos+x+y*a.ancho), idntico al anterior


sustituyendo los puntos por flechas ->.
En realidad se trata de la misma implementacin salvo que en la primera el tipo MATRIZ
es un puntero a una estructura, por tanto es necesario primero reservar memoria para poder utilizar
la estructura, mientras que en la segunda implementacin, el tipo MATRIZ es ya en si la estructura,
con lo cual el compilador ya reserva la memoria necesaria.
En el primer ejemplo MATRIZ a; define a como un puntero a una estructura struct mat,
mientras que en la segunda MATRIZ a; define a como una variable cuyo tipo es struct mat.

Operaciones con la memoria <memory.h>


Finalmente vamos a ver un resumen de algunas funciones bsicas (sus prototipos) de
memoria. No son funciones estrictamente de cadenas, pero tienen su prototipo en #include
<string.h>:

void *memchr(void *s, int c, size_t n)

Busca un caracter en un buffer.

int memcmp(void *s1, void *s2, size_t n)

Compara dos buffers.

void *memcpy(void *dest, void *fuente, size_t n)

Copia un buffer dentro de otro.

void *memmove(void *dest, void *fuente, size_t n) 

Mueve n bytes de un buffer a otro.

void *memset(void *s, int c, size_t n)

Pone todos los bytes de un buffer a un


caracter dado.

El uso de estas funciones es directo y parecido a las operaciones de comparacin de


caracteres (excepto que la longitud exacta (n) de todas las operaciones deber ser indicada ya que
no hay una forma propia de terminacin).

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.

Ficheros (usar el Disco


Duro)

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);
}

Es importante no olvidar usar la ltima sentencia, fclose( ), ya que es la encargada de


guardar los cambios en el archivo (como cuando pulsas guardar en un procesador de textos). Su
argumento siempre ser el puntero que apunte al archivo.
Ahora, una vez finalizado el programa, podrs ver que en el mismo lugar en el que has
ejecutado el programa, ha aparecido un archivo de texto con esa frase.
FILE *puntero;
f = fopen ("tontera.txt", "w");
if (puntero = = NULL)
printf ("Error, el fichero no existe.");

Para saber si se cre el archivo


correctamente, existe un algoritmo sencillo: consiste
en preguntar si al puntero que apunta a nuestro
archivo le han devuelto un NULL, lo que quiere decir
que hubo un problema y no se cre.

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+

Abre un archivo de texto para lectura. Si el archivo no existe, da un error.


Abre para lectura / escritura. Si el fichero no existe, da un error.
Abre para escritura. Si el fichero no existe, lo crea, y si existe lo machaca.
Abre para lectura / escritura. Si el fichero no existe, lo crea, y si existe lo machaca.
Abre para aadir al final. Si el fichero no existe, lo crea.
Abre para aadir al final, con opcin de lectura. Si el fichero no existe, lo crea.

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.

putw ( ): escribe un valor entero en un archivo. Su sintaxis es: putw(entero, puntero);


fputs ( ): escribe un string en un fichero de texto. Su sintaxis es: fputs (string, puntero);
fprintf ( ): funciona igual que un printf pero guarda
la salida en un fichero.

#include <stdio.h>
main( )
{
FILE *punt;
char nombre[20] = "Martin";
char nuevo[20];
int edad = 19;
int nueva;

Como parmetros, lo primero es colocarle el


puntero que apunta al archivo, para que as
coloque
el
letrero
y
los
argumentos
correspondientes en l.
El resto es la rutina de siempre, los moldes
para los valores de las variables, e imprimir el
letrero resultante, solo que ahora en vez de
hacerlo en la pantalla, lo hace en un fichero.

/* Uso de la fprintf ( ) */
punt = fopen("datos.666", "wb");
fprintf(punt,"%s %i", nombre, edad);
fclose(punt);

El fichero, adems de ponerle una extensin


rara, lo he escrito en modo binario, tratando de
evitar que se modifique de manera sencilla.

/* 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 &.

punt = fopen("datos.666", "rb");


fscanf(punt, "%s %i", nuevo, &nueva);
printf("Nombre --> %s\n", nuevo);
printf("Edad --> %i", nueva);
fclose(punt);

Simplemente lee los datos siguiendo el molde


indicado entre parntesis, los guarda en sus
variables y luego los saco por pantalla

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);
}

En el siguiente ejemplo, lo que


hago es leer los datos de nuestro archivo
de texto, usando un bucle que coge uno a
uno todos los caracteres.
A la hora de abrir el archivo, he
usado el modo r (lectura). La funcin getc
devolver EOF en caso de fin de archivo
o error de algn tipo.

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 (&registro, 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);

Una vez introducidos los datos, pedimos


con la fwrite que los datos guardados en la
direccin de grupo, que ocupan un espacio
igual al tamao de grupo, y que forman un
registro (una estructura), apuntado por el
puntero punt, los guarde en el archivo que
tenga abierto, y el nico que tiene abierto es
el de grupos.wea.
Por ltimo, cerramos y guardamos los
cambios con la fclose y se acab.

fwrite(&grupo, sizeof(grupo), 1, punt);


fclose(punt);
}

Con este programa solo guardamos informacin para un solo registro, y para guardar para
un montn, con hacer un bucle, solucionado.

fread ( ): sus argumento son los mismos que la


fwrite.
Al tratarse de otro programa diferente,
para poder leer correctamente los datos del
fichero que habamos creado con el ejemplo
de la fwrite anterior, debemos definir otra vez
la estructura y abrir el fichero en modo
lectura.
De esta manera, va leyendo los datos que
se ajustan a los moldes de las printf que los
piden. De otra manera, esto dara errores por
un tubo.
Como siempre, al final se debe cerrar el
archivo.

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);

rename ( ): cambiar el nombre de un archivo. Necesita


que se le ponga entre comillas (por ser cadenas de
texto) el nombre del archivo al que le vas a cambiar el
nombre, y despus el nombre que quieres ponerle.
En el ejemplo pongo las variables que los contienen,
para que sea el usuario quien decida los nombres.
En caso de que no se pueda renombrar (hay otro
archivo que ya se llama as, est protegido, etc),
devolver un -1.
En este otro ejemplo, comprobamos qu tipo de
errores devuelve. La funcin perror( ); imprime en
pantalla el tipo de error sucedido, devuelto por rename
EACCES

Permission denied: filename already


exists or has an invalid path

ENOENT

No such file or directory

ENOTSAM

Not same device

char viejo[80], nuevo[80];


printf("Archivo a renombrar: ");
gets(viejo);
printf("Nuevo nombre: ");
gets(nuevo);
rename(viejo, nuevo);

char viejo[80], nuevo[80];


printf("Archivo a renombrar: ");
gets(viejo);
printf("Nuevo nombre: ");
gets(nuevo);
if (rename(viejo, nuevo) = = 0)
printf("Correcto);
else
perror("rename");

remove ( ): elimina un archivo. Su sintaxis sera: remove (nombre de archivo); Si se hace


correctamente devolver 0, y si no, otro valor.
fseek ( ): su sintaxis sera:
fseek (puntero, numero de bytes, origen);
Lo primero es el puntero al archivo, despus el nmero de bytes desde origen hasta la
nueva posicin, y por ltimo el origen. Puede ser:

Valor
0
1
2

Resultado
comienzo del archivo
posicin actual
fin de archivo

Definida en <stdio.h> como...


SEEK_SET
SEEK_CURT
SEEK_END

Esta funcin establece el localizador de la posicin del archivo, y devuelve un 0 si se


realiza bien. El nmero de bytes debe ser un entero largo para soportar operaciones sobre
archivos que son mayores de 64 kb.
Ejemplo: Quiero posicionarme en la posicin penltima del fichero "f" .
fseek( f, -1, SEEK_END );
ferror ( ): determina si una operacin con archivos produce un error. Su sintaxis es:
ferror (puntero);
Si la operacin anterior a ferror sali bien, devolver un 0, pero si no, otro valor.
rewind ( ): reestablace el localizador de posicin al comienzo del archivo que se especifique
como argumento. Su sintaxis es:
rewind (puntero);
ftell ( ): devuelve la posicin actual, expresada en bytes, a partir del principio del fichero, o un -1
en caso de error. Su sintaxis:
ftell (puntero);
Ejemplo: declaramos un entero largo para
almacenar
el nmero de bytes que nos
devuelva la funcin, y sacamos el resultado con
una printf.

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.

while ( !feof (punt) )


{
fread (....);
...
}

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








el archivo se abrir en modo de slo aadir.


si el archivo no existe, ser creado.
abrir en forma exclusiva.
slo lectura. (1)
lectura y escritura. (4)
slo escritura. (2)

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

Archivo de solo lectura.


Archivo oculto.
Archivo del sistema.
Nombre de etiqueta de volumen.
Nombre de subdirectorio.
Archivo.
No usados.

write ( ): sirve para escribir y su prototipo es:


write(int fd, char *buffer, int longitud);
Cada vez que el cacharro ejecuta un write, escribe un nmero de caracteres
(longitud) desde la memoria intermedia (apuntada por buffer) hasta el archivo de disco (fd).
Devuelve el nmero de bytes que escribi 1 si falla.

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 );

En este programa la funcin


freopen cierra el fichero stdout, que es la
pantalla, y lo abre apuntando al fichero
resultado.txt.
Escribir en el fichero mientras
no escribamos la palabra salir.
Y ya lo que hago es rellenar este
hueco tan feo que me queda.

fprintf( stderr, "El usuario ha tecleado 'salir'" );


}

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;

/* Estructura modelo para informacion sobre un proveed. */


/* Codigo de proveedor (igual para prov. iguales).*/
/* Nombre del proveedor. */
/* Direccoin comleta del proveedor. */
/* Telefono del proveedor. */
/* Otros datos de interes. */

...
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);

/* Estas lineas calculan el tamao del fichero en bytes. */


/* Ya tenemos su tamao. */

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

En este tema he recogido los algoritmos o programas ms bsicos y utilizados en el desarrollo de


programas en C.
Conversin de un nmero decimal a hexadecimal:
El nmero escrito por el usuario se divide en dos mitades, y cada mitad tiene cuatro bits
que contienen un nmero del 0 al 15.
Usando un switch los valores del 10 al 15 se muestran en pantalla con la letra
correspondiente (de la A a la F).
Si por ejemplo introducimos un 100, el programa lo convierte en hexadecimal dando un
valor 64. En ese caso, no usara el switch, ya que ninguna de las dos mitades es mayor que 9. Sin
embargo si entrara dentro del switch en el caso de que el usuario introdujese un 200.
#include <stdio.h>
void ahex(int n);
main( )
{
int n;
int a,b;

/* Nmero ledo */
/* Almacenamiento temporal */

printf("Introduzca un entero del 0 al 255 => ");


scanf("%i",&n);
a = n / 16;
b = n % 16;

/* Obtiene el valor de los 4 bits superiores. */


/* Obtiene el valor de los 4 bits inferiores. */

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");
}
}
}

Ordenacin de cadenas de caracteres


El conjunto de cadenas est almacenado en memoria como una matriz bidimensional de
caracteres (una matriz bidimensional con cinco nombres de una longitud mxima de 7 caracteres
cada uno).
Cada nombre dentro de la matriz puede referenciarse usando como ndice el nmero de la
fila que ocupa.
La tcnica usada para ordenar la lista de nombres se llama burbuja. Consiste en un bucle
anidado que hace n 1 pasadas sobre una lista de n elementos, de forma que cambia el elemento
X por el X + 1.

#include <stdio.h>
#include <string.h>
#define NUM 5
main()
{

291
325
330
333

char nombres[5][7] = { {'s', 'u', 's', 'a', 'n', 'a'},


{'m', 'i', 'g', 'u', 'e', 'l'},
{'j', 'a', 'v', 'i', 'e', 'r'},
{'j', 'u', 'a', 'n'},
6.20
{'j', 'a', 'v', 'i'} };
char nombre_aux[7];
7.10
int j,k;
7.11
printf("La lista original es:\n");
for(j = 0; j < NUM; j++)
7.12
printf("%s\n",nombres[j]);
for(j = 0; j < NUM - 1; j++)
337
7.13
for(k = j + 1; k < NUM; k++)
if(strcmp(nombres[j],nombres[k]) > 0)
{
strcpy(nombre_aux,nombres[j]);
strcpy(nombres[j],nombres[k]);
strcpy(nombres[k],nombre_aux);
}
printf("\nLa lista ordenada es:\n");
for(j = 0; j < NUM; j++)
printf("%s\n",nombres[j]);

Cuando lo ejecutemos, nos sacar la lista original y despus el siguiente resultado:


La lista ordenada es:
javi
javier
juan
miguel
Susana

Estructuras enlazadas Consiste en ir creando una serie de estructuras en la RAM, enlazadas


entre s mediante un puntero. El programa es el siguiente:
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo
{
char dato;
struct nodo *enlace;
} LISTA;
void insertar(LISTA **ptr, char elemento);
void mostrar_lista(LISTA *ptr);
main( )
{
LISTA *n1 = NULL;
char elemento;
do
{

printf("\nIntroduzca elemento (punto . para terminar): ");


fflush(stdin);
elemento = getchar();
if(elemento != '.')
insertar(&n1, elemento);
} while(elemento != '.');
printf("\nLa nueva lista enlazada es: ");
mostrar_lista(n1);
}
void insertar(LISTA **ptr, char elemento)
{
LISTA *p1, *p2;
p1 = *ptr;
if(p1 == NULL)
{
p1 = malloc(sizeof(LISTA));
if (p1 != NULL)
{
p1->dato = elemento;
p1->enlace = NULL;
*ptr = p1;
}
}
else
{
while(p1->enlace != NULL)
p1 = p1->enlace;
p2 = malloc(sizeof(LISTA));
if(p2 != NULL)
{
p2->dato = elemento;
p2->enlace = NULL;
p1->enlace = p2;
}
}
}
void mostrar_lista(LISTA *ptr)
{
while(ptr != NULL)
{
printf("%c",ptr->dato);
ptr = ptr->enlace;
}
printf("\n");
}

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.

Como se ve en el dibujo, la primera nos enva a la segunda, y as hasta que el programa


de con un NULL.
Para empezar definimos una estructura en la cual guardaremos un carcter que introduzca
el usuario y un puntero llamado enlace que apuntar a una estructura de tipo nodo (igual que en la
que estamos), y decimos que para referirnos a esa estructura usaremos la palabra LISTA.
Dentro de main creamos otro puntero que apunte a una estructura del tipo LISTA e
inicializado con el valor NULL, y un carcter llamado elemento. Pedimos un primer carcter, que
ser capturado por getchar tras haber limpiado el buffer con nuestra fflush.
Si el elemento es diferente de un punto, llama a la funcin insertar mandndole la direccin
en que se encuentra declarado n1 y el carcter del usuario. Todo esto lo har mientras el carcter
sea diferente de un punto.
Como le hemos enviado la direccin de un puntero que apunta a algo, esto deber recibirlo
en la funcin insertar un puntero que va a apuntar a otro puntero (**ptr). Dentro de la funcin
declaramos tambin otros dos puntero ms que apunten a LISTA, llamados p1 y p2, aunque ste
ltimo no nos va a servir por ahora. A p1 le asignamos el contenido de a lo que apunte ptr, y como
ptr apunta a n1 el cual apunta a NULL, pues en p1 se pone un NULL.
Ahora preguntamos si p1 contiene un NULL, y vemos que si, por lo que entramos en el
bloque de sentencias de la if. Ahora vamos a darle un nuevo uso a p1 dndole una direccin en la
que se pueda guardar una nueva estructura del tipo LISTA.
Si la malloc no ha devuelto NULL (queriendo decir esto que tenemos espacio en la RAM
para meter una estructura nueva) entonces en la estructura apuntada por p1, en la variable dato
metemos el carcter y en la variable enlace ponemos un NULL. Por ltimo, en el lugar a donde
apunte ptr (apunta a n1) metemos la direccin contenida en p1.
Bien, si no te has perdido, te resultar no muy difcil seguir t solo. Te recomiendo que te
confecciones una tabla con los valores que van tomando las variables, para que veas como va
funcionando y enlazando el programa. Si tienes Borland C++, usa el Turbo Debugger. Aqu va una
tabla con los posibles valores:
Elemento
q
w
e
r

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:

Operaciones bsicas sobre listas enlazadas:


Los prximos tres programas contienen listas predefinidas cuyo objetivo es ilustrar las
operaciones bsicas que se realizan sobre listas enlazadas (insertar, borrar y buscar).
INSERTAR
/* Programa que muestra como insertar una letra al comienzo de una lista enlazada compuesta por tres
elementos */
#include <stdio.h>
#include <stdlib.h>
typedef struct nodo
{
char dato;
struct nodo *enlace;
} LISTA;
void mostrar_lista(LISTA *ptr);
void insertar_al_principio(LISTA **ptr, char item);
void insertar_al_final(LISTA *ptr, char item);
main( )
{
LISTA *n1, *n2, *n3;
n1 = malloc(sizeof(LISTA));
n2 = malloc(sizeof(LISTA));
n3 = malloc(sizeof(LISTA));
n1->dato = 'c';
n1->enlace = n2;
n2->dato = 'a';
n2->enlace = n3;
n3->dato = 't';
n3->enlace = NULL;
printf("La lista enlazada es como sigue: ");
mostrar_lista(n1);
insertar_al_principio(&n1,'s');
printf("La nueva lista enlazada es: ");
mostrar_lista(n1);
insertar_al_final(n1,'m');
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 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");
}
...

int buscar_en_lista(LISTA *ptr, char item)


{
if(ptr == NULL)
return(0);
else
{
do
{
if(ptr->dato == item)
return(1);
ptr = ptr->enlace;
} while(ptr != NULL);
return(0);
}
}

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>

De esta manera, nosotros podemos


asignar a una variable de tipo char la letra
a de dos maneras:

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.

for(contador = 32; contador < 255; contador++)


{
printf("%c ", caracter);
caracter ++;
}

Debemos indicar que contamos en


hexadecimal poniendo el 0x delante.

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

Efectivamente, al ser cambiado el quinto bit a 0 da como resultado la mayscula de la letra


anterior.

Cdigos ASCII (0(0-127).

Carcteres no imprimibles
Nombre

Carcteres imprimibles

Dec Hex Car. Dec Hex Car.

Dec Hex Car. Dec Hex Car.

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

Escape lnea de datos

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

Fin bloque transmitido

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.

De todas las opciones


topics que nos aparecen a la
izquierda
de
la
ventana,
escogemos Debugger. A la
derecha nos aparece un espacio
para poner los argumentos que
queramos
meterle
(Run
arguments), pero NO hace falta
poner el path, ya que se asigna el
solo automticamente en el
argv[0], y los argumentos que
escribas se guardarn a partir del
uno (argv[1]).

#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.

Proyectos bajo Borland C++ v4.02


Un proyecto es un conjunto de archivos relacionados para generar un progama.
Una vez abierto el editor de Borland pulsamos PROJECT y despus la opcin NEW PROJECT.
Nos aparece una ventana que pone New Target, y dentro de ella, tenemos las siguientes opciones:
-Project Path and Name: aqu ponemos la direccin y el nombre del nuevo proyecto.
-Target Name: el nombre del programa que obtendremos al final.
-Target Type: el tipo de programa que obtendremos al final, como por ejemplo aplicaciones
ejecutables o libreras.
-Plattform: aqu elegimos en qu sistema operativo o plataforma deber correr nuestro resultado.
Tenemos los Windows, el DOS (standard) que es el que uso ms, y el DOS (overlay) que es algo
as como DOS pero de solapamiento, para memorias pequeas.
-Target Model: tamaos de RAM que vamos a emplear. En orden ascendente son: tiny, small,
medium, compact, large y huge.
Esto dar un *.ide o proyecto del que colgaremos todos los programas que queramos
meterle. Tenemos el principal (el que tiene el main), y queremos colgarle ms: view, project, y en la
ventana pulsamos con el botn derecho del ratn encima del nombre del proyecto. La opcin
delete node quita un nodo, add node aade.

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:

Cuando se compila este programa,


el compilador sustituir la expresin definida
por MIN(x,y), excepto que x e y sern
usados como los operandos.

#define MIN(a,b) (a < b) ? a : b


main( )
{
int x = 10, y = 20;

As despus de que el compilador


hace la sustitucin, la sentencia printf ser
sta:

printf("El minimo es %i", MIN(x,y) );


}

printf("El minimo es %i, (x < y) ? x : y);


Como se puede observar donde se coloque MIN, el texto ser reemplazado por la
definicin apropiada. Por lo tanto, si en el cdigo se hubiera puesto algo como:
x = MIN(q + r, s + t);
despus del preprocesamiento, el cdigo podra verse de la siguiente forma:
x = ( q + r < s + t ) ? q + r : s + t;
#undef: quita una definicin de nombre de macro que se haya definido previamente. El formato
general es:
#undef <nombre de macro>
El uso principal de #undef es permitir localizar los nombres de macros slo en las
secciones de cdigo que los necesiten.
#include: instruye al compilador para incluir otro archivo fuente que esta dado con esta directiva y
de esta forma compilarlo todo junto. El archivo fuente que se leer se debe encerrar entre
comillas dobles o parntesis de ngulo. Por ejemplo:
#include <archivo>

#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>

La nica diferencia por ejemplo con exit( ) es


que cuando se ejecuta, devuelve por pantalla un
mensaje de error informando que el programa fue
abortado. Otra funcin de finalizacin de programa es
assert( ), explicada aqu debajo.

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');

Para que la funcin funcione (valga


la redundancia), debemos incluir la librera
<assert.h>.
Si, tras la pregunta, el usuario
pulsa cualquier tecla distinta de n, la
funcin assert devuelve un mensaje (en
stderr) indicando la condicin que no se ha
cumplido, el archivo fuente en que se
encuentra y la lnea exacta de su
ubicacin.
Adems llama ella misma a la
funcin abort para que aborte el programa.

Ejecutar comandos con un programa en C


system (comando) : Ejecuta el comando
indicado entre parntesis. Esto
incluye tanto los comandos del
sistema operativo, como cualquier
programa
que
nosotros
le
indiquemos. Al acabar la ejecucin
del comando, volver a la lnea de
cdigo situada a continuacin de la
sentencia system.
#include <stdlib.h>

...
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);
...

Color de texto y de fondo


En este apartado explico como podemos, en un programa como proyecto bajo MS-DOS,
sacar por pantalla un texto de un determinado color y con un color de fondo. Las funciones son las
siguientes (incluidas en <conio.h>):
#include <conio.h>

textcolor( ): sirve para que le indiquemos el nmero de color


que queremos para el texto. Son los siguientes:
BLACK
BLUE
GREEN
CYAN
RED
MAGENTA
BROWN
LIGHTGRAY
DARKGRAY

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

textbackground( ): sirve para rellenar del color


especificado entre parntesis el resto del dibujo (su
smbolo ASCII) de las letras que vayamos a sacar por
pantalla, es decir, el fondo.
cprintf( ): saca texto por pantalla con el color y fondo
actual. La diferencia con printf es que no le sirven los
moldes \n, \r, \t, etc.
NOTA: la funcin window( ) del primer ejemplo lo que hace
es redefinir el recuadro de que dispone nuestro programa
para sacar texto, en nuestro caso desde el punto (10, 10)
hasta el (80, 25).
En el siguiente ejemplo, lo que se hace es mostrar
un montn de lneas con las posibles combinaciones de
colores con las funciones que hemos visto.

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);
}
}

Password (claves de acceso)


Existen dos maneras de pedir al
usuario una clave de acceso al programa.
Una de ellas es usando la funcin getpass( ).
Imprime en pantalla el letrero que
metamos entre parntesis y captura una
cadena.
No produce eco en la pantalla, lo cual
resulta un poco feo, ya que mola ms que
vaya poniendo asteriscos, pero bueno
.

#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");
}

La otra manera sera


usando el algoritmo de Martinev
Lopenski para la introduccin de
clave con asteriscos y pudiendo
usar la tecla de retroceso (ah
llevas).
Definimos como clave una
cadena de caracteres cualquiera.
Abrimos main y declaramos un
cadena de 10 caracteres llamada
intento
que
por
ahora
la
llenaremos de caracteres \0.
Tambin necesitaremos de un
centinela que lleve la cuenta de las
veces que intenta el usuario
escribir la clave.

#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) => ");

Mientras que el centinela


sea menor que 3, realizaremos
una captura de cadena carcter a
carcter. De no pulsar un <Intro> o
un retroceso, guardamos el
carcter
en
la
cadena
e
imprimimos un asterisco

for(contador = 0; contador <= 9; contador++)


{
fflush(stdin);
letra = getch();
if(letra = = '\r')
{
intento[contador] = '\0';
contador = 100;
continue;
}

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++;
}

Ahora lo que hacemos es


comparar la cadena capturada con
la clave. De ser correcta, nos
saldr un mensaje que nos diga
correcto,
y
el
programa
terminar.
Si la comparacin
nos
devuelve un valor distinto de 0 (es
decir, que no son iguales las
cadenas comparadas), entonces
nos dice que es incorrecta, espera
que pulsemos una tecla y suma 1
al centinela. Si ste sigue siendo
menor de 3, seguir en el bucle; si
no, el programa terminar.

else
{
printf("\n\t\tCorrecto");
exit(1);
}
if(centinela = = 3)
exit(1);
}
}

Los pitidos de la CPU


La CPU es capaz de generar una serie de pitidos (por ejemplo, cuando pulsas varias teclas
a la vez, cuando reinicias el cacharro, etc). La forma de sacar el pitido con un programa de C es
con la funciones siguientes (no olvides que debe ser un proyecto bajo MS-DOS):
sound( ): genera un pitido con la frecuencia que le
indiquemos entre parntesis. sta puede ir desde 0 hasta
30.000.
delay( ): lo que hace es esperar un determinado tiempo,
en milsimas de segundo, que le hemos puesto entre
parntesis.
nosound( ): como el pitido generado por la funcin sound
no se detiene, lo que hemos hecho ha sido hacer que dure
un cuarto de segundo, y para detenerlo usamos la funcin
nosound, que apaga el pitido.

#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 */

#define silencio 30000

/* silencio emitido en ultrasonido */

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;

/* Negra, un paso normal */


/* Blanca, vale dos negras */
/* Redonda, vale cuatro negras o dos blancas */
/* Corchea, vale media negra */
/* Semicorchea, vale un cuarto de negra /*
/* Fusa, vale un octavo de negra */
/* Semifusa, vale un dieciseisavo de negra */
/* Negra con puntillo, como una negra y media */
/* Blanca con puntillo */
/* Redonda con puntillo */
/* Corchea con puntillo */
/* Semicorchea con puntillo */
/* Fusa con puntillo */
/* Semifusa con puntillo */
/* Tresillo de negras, tres en el tiempo de dos */
/* Tresillo de corcheas */
/* Tresillo de semicorcheas */

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;

La estructura empaquetado contiene 6 miembros:


4 banderas de 1 bit (b1, b2, b3 y b4), uno de 4 bits
(tipo) y otro de 9 bits (ent_raro).
C automticamente empaca los campos de bit
anteriores tan compactamente como sea posible,
donde la longitud mxima del campo es menor que o
igual a la longitud de la palabra entera de la
computadora. Si no fuera el caso, entonces algunos
compiladores podran permitir traslape en memoria
para los campos, mientras otros podran guardar el
siguiente campo en la siguiente palabra.

La forma de acceder a los miembros es en la forma usual:


paquete.tipo = 7;
Solamente los n bits ms bajos son asignados a un nmero de n bits. Por lo tanto, el
campo tipo no puede tomar valores mayores que 15, ya que es de 4 bits de largo. Los campos de
bit son siempre convertidos a un tipo entero cuando se hace algn clculo con ellos. Se permite
mezclar los tipos ``normales'' con los campos de bit.

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.

random ( ): esta es una funcin que genera un


nmero aleatorio entre el 0 y el nmero de
dentro del parntesis, restndole 1.
#include <stdlib.h>

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
main( )
{
for( ; ; )
{
printf("%i\n", random (100));
getch( );
}

Es decir, que el ejemplo genera una y


otra vez nmeros entre el 0 y el 99.
El usuario solo tiene que pulsar
cualquier tecla para que le vayan saliendo
nmeros aleatorios de ese intervalo.

randomize ( ): Inicializa el generador de nmeros aleatorios. Deberemos llamarlo al inicio de la


funcin en que utilicemos el random. Tambin deberemos utilizar el include time.h, ya que
randomize hace una llamada a la funcin time, incluida en este ltimo archivo.
#include <stdio.h>
Una tcnica comn para introducir ms aleatoriedad en un generador de nmeros
aleatorios es usando el tiempo y la fecha, ya que ste siempre estar cambiando.
rand( ): La funcin devuelve un entero pseudo-aleatorio entre 0 y RAND_MAX. Su prototipo es:
int rand(void);
srand( ): Establece su argumento como la semilla de una nueva serie de enteros pseudoaleatorios. Su prototipo sera:
void srand(unsigned int semilla);
Un ejemplo sencillo del uso del tiempo de la fecha es inicializando la semilla:
srand( (unsigned int) time( NULL ) );
#include <stdio.h>
#include <conio.h>
#include <graphics.h>
#include <dos.h>
#include <stdlib.h>
void main(void)
{
int controlador, modo, x;
randomize( );
detectgraph(&controlador, &modo);
initgraph(&controlador,&modo,"C:\\BC4\\BGI");
for (x=1;x<1000000;x++)
{
setcolor(random(15));
moveto(random(630),random(470));
outtext("#");
}
}

Para pillar este ejemplo, se hace


necesario que te mires el tema de
grficos, por que si no, esto te va a sonar
a chino.
Declaramos nuestras variables
para el modo grfico y un entero x que
nos servir de contador.
Inicializamos el generador de
nmeros aleatorios con la funcin
randomize().
Por cada vuelta del bucle, el
programa dibujar en la pantalla, con las
coordenadas escogidas aleatoriamente.
Es como un salva-pantallas, pero de los
que te rayan la cabeza.
Ms ejemplos:

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?

printf("100 Numeros Aletorios Entre 10 y 70\n\n");


for(i=1; i<100; i++)
{
x=rand () % (60) + 10;
printf("%i ",x);
a=a+x;
}
a = a / 100;
printf("\n\nLa media de los numeros aleatorios obtenidos es %d",a);
}

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);
}

Obtener la hora del sistema


Para hacer esto, deberemos
primero crear una estructura del tipo
time, compuesta por una serie de
enteros que representan la hora, minutos
y segundos entre otras.
Despus recurrimos a la funcin
gettime( ), que captura la hora del
sistema y la guarda en nuestra estructura
(en el ejemplo la hemos llamado t).

#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).

Leer las teclas especiales del teclado


La mayora de las teclas del teclado, despus de una transformacin por un programa
interno, generan un cdigo de un solo carcter que corresponde al cdigo ASCII, excepto las teclas
de funcin.
Las teclas de la F11 a la F20 son equivalentes a pulsar <shift> F1, <shift> F2, etc, mientras
que de la F21 a la F30 equivalen a pulsar <ctrl> F1, <ctrl> F2, etc. Estas teclas generan dos
caracteres: el primero es 0x00, y el segundo depende de la tecla.
Para utilizar estas teclas, un programa debe trabajar en modo de lectura carcter a
carcter, es decir, usando la getch( ), de manera que primero se compruebe que el carcter
recibido es 0x00 y, en caso afirmativo, volver a llamar a getch( ) para leer el carcter siguiente.
Las teclas de retorno de carro, escape, tabulacin, retroceder, nueva lnea y seal acstica
corresponden a los valores hexadecimales 0x0D, 0x1B, 0x09, 0x08, 0x0A y 0x07 respectivamente,
siendo teclas de un solo carcter. El resto de caracteres especiales generan el 0x00 ms el valor
de la siguiente tabla:

Interrupciones (usar los


Perifricos)

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, &regs, &regs);
}
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:

Shift dcho pulsada


Shift izdo pulsada
Control pulsada
Alt pulsada
Bloq Despl pulsada
Bloq Num pulsada
Bloq Mayus pulsada
Insert pulsada

- Ajustar factor de repeticin


Nmero de interrupcin: 16h
Nmero de funcin: 3
Entrada: AH = 3
AL = 5
BH = retardo
BL = factor de repeticin
Salida: No tiene
Nota: con esta funcin se puede modificar la velocidad del teclado cuando se mantiene pulsada
una tecla.
Retardo indica el tiempo que se espera hasta reaccionar cuando una tecla se mantiene
pulsada y el factor de repeticin indica cuantas veces se repetir por segundo dicha tecla tras
pasar el tiempo de retardo.
Retardo puede tomar los valores 0 (=0.25 s), 1 (=0.5 s), 2 (=0.75 s) y 3 (=1 s). El factor de
repeticin puede tomar valores entre 0 (=30 cps) y 31 (=2 cps)

La interrupcin del ratn


La tcnica con la que se puede gestionar el ratn de un PC la tom prestada Microsoft de
la interfaz de programacin del DOS y de la BIOS, es decir, mediante una interrupcin especial. En
este caso, la interrupcin 33h.
De esta forma, cualquier programa gestiona el ratn sin necesidad de acceder al nivel
hardware. Cada una de estas funciones se caracterizan por el valor que se asigne al registro AX
cuando se llame a la INT 33h.
El cursor del ratn se mueve hacia el centro de la pantalla y despus se oculta. En el modo
grfico aparece como una flecha mientras que en el modo texto aparece como un rectngulo
invertido. Los controladores de eventos se desactivan.

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.

- Reset del driver del ratn


Entrada: AX = 0000h
Salida: AX = estado
0000h hardware/driver no instalado
FFFFh hardware/driver instalado
BX = nmero de botones
- Averiguar el tipo de ratn instalado

#include <stdio.h>
#include <dos.h>
main( )
{
union REGS registros;
registros.x.ax = 0;
/* Funcion */
int86 (0x33, &registros, &registros);
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.

- Ocultar cursor del ratn


Entrada: AX = 0002h
Nota: Esta funcin decrementa 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.
Mediante llamadas sucesivas a esta funcin, el contador se decrementar de forma
continua de modo que sera necesario llamar a la funcin 0001h ese mismo nmero de veces
sucesivas para que el contador vuelva a alcanzar el valor 0 y se pueda visualizar el cursor del
ratn.
- Leer el estado de los botones y la posicin del cursor
Entrada: AX = 0003h
Nota: Esta funcin nos permite conocer el estado de los botones del ratn, informacin que
almacena en el registro bx de la siguiente forma:
Bit 1: Botn derecho ( 1 = presionado ).
Bit 0: Botn izquierdo ( 1 = presionado ).
Ademas, esta funcin deja en el registro cx la coordenada X y en el registro dx la
coordenada Y.
- Establecer la posicin del ratn
Entrada: AX = 0004h
Nota: A esta funcin se le debe pasar la coordenada horizontal en el registro cx, y la vertical en el
dx. Cuando se trabaja en modo texto hay que tener en cuenta, que las coordenadas hay que
multiplicarlas por 8 antes de pasarlas a la funcin.
- Determinar el rango de movimiento horizontal del ratn
Entrada: AX = 0007h
CX = mnima posicin horizontal del ratn
DX = mxima posicin horizontal del ratn
- Determinar el rango de movimiento vertical del ratn
Entrada: AX = 0008h
CX = mnima posicin vertical del ratn
DX = mxima posicin vertical del ratn

- Definir el aspecto del cursor del ratn en modo grfico


Entrada: AX = 0009h
BX = distancia del punto de referencia (hot spot) del borde izquierdo del campo de
bits.
CX = distancia del punto de referencia (hot spot) del borde superior del campo de
bits.
ES:DX bitmap (16x16 bits)
16 words mscara de pantalla (16x16 bits)
16 words mscara del cursor (16x16 bits)
Nota: El campo de bits comprende 32 words (=64 bytes) de los cuales los primeros 16 words se
combinan mediante la funcin lgica AND con los bits existentes en la pantalla y los siguientes 16
lo hacen mediante la funcin lgica OR.
- Definir el aspecto del cursor del ratn en modo texto
Entrada: AX = 000Ah
BX = tipo de cursor de texto (hardware / software)
0000h software
CX = mscara de pantalla
DX = mscara del cursor
0001h hardware (cursor intermitente habitual de la E/S de caracteres)
CX = lnea inicial
DX = lnea final
Nota: El cursor hardware es el que aparece por defecto (el tpico cuadradito). Se puede modificar el
tamao del cursor hardware para que el cuadrado sea ms pequeo o ms grande. Cuando se
selecciona el modo software podemos usar un carcter ASCII como cursor.
En este caso, el cdigo del carcter que se encuentre en ese momento bajo el cursor del
ratn y el byte de atributos asociado al mismo se operan con la mscara de pantalla mediante
una funcin lgica AND.
Posteriormente se operan con la mscara del cursor mediante una funcin lgica XOR. El
formato de un carcter en pantalla es el siguiente (busca en la ayuda de C textattr):
Bit
15:
14-12:
12-8:
7-0:

Efecto
.
parpadeo (blink)
color de fondo (background)
color del carcter (foreground)
cdigo ASCII del carcter

- Fijar relacin entre mickeys y puntos


Entrada: AX = 000Fh
CX = nmero de mickeys por 8 puntos horizontalmente (por defecto 8)
DX = nmero de mickeys por 8 puntos verticalmente (por defecto 16)

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

- Obtener el nmero de veces que se liber un botn del ratn


Entrada: AX = 0006h
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 liberado desde la ltima
llamada a esta funcin
CX = columna donde se liber el botn especificado por ltima vez
DX = fila donde se liber el botn especificado por ltima vez
- Obtener los valores de los contadores de movimiento
Entrada: AX = 000Bh
Salida: CX = nmero de mickeys recorridos horizontalmente desde la ltima llamada
DX = nmero de mickeys recorridos verticalmente desde la ltima llamada
Nota: Un valor positivo en CX (DX) indica que el ratn se ha movido hacia la derecha (abajo). Con
la funcin 000F se puede establecer la equivalencia entre mickeys y puntos.

- Instalar un controlador de eventos


Entrada: AX = 000Ch
CX = mscara de los eventos que queremos que gestione el controlador
bit 0: si el ratn se mueve
bit 1: si se pulsa el botn izquierdo
bit 2: si se libera el botn izquierdo
bit 3: si se pulsa el botn derecho
bit 4: si se libera el botn derecho
bit 5: si se pulsa el botn central (Mouse Systems/Logitech mouse)
bit 6: si se libera el botn central (Mouse Sys/Logitech mouse)
ES:DX

Direccin donde comienza la funcin del controlador que deseamos


instalar

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)

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 donde se encuentra el cursor del ratn
DX = fila donde se encuentra el cursor del ratn
SI = distancia en mickeys del ltimo movimiento horizontal del ratn
DI = distancia en mickeys del ltimo movimiento vertical del ratn
- Instalar un controlador de eventos alternativo para combinacin con Alt / Shift / Ctrl
Entrada: AX = 0018h
CX = mscara de los eventos que queremos que gestione el controlador
alternativo
bit 0: si el ratn se mueve
bit 1: si se pulsa el botn izquierdo
bit 2: si se libera el botn izquierdo
bit 3: si se pulsa el botn derecho
bit 4: si se libera el botn derecho
bit 5: si la tecla Shift est pulsada durante el evento
bit 6: si la tecla Control est pulsada durante el evento
bit 7: si la tecla Alt est pulsada durante el evento
ES:DX  Direccin donde comienza la rutina del controlador alternativo que
queremos instalar
Salida: AX

= 0018h: Ok
= FFFFh: error

Nota: El controlador de eventos alternativo es similar al controlador de eventos anteriormente


descrito.
La diferencia es que se activa cuando ocurre un evento y se est pulsando
simultneamente una determinada tecla (Alt, Shift o Ctrl.) por lo que al menos uno de los bits 5-7
debe estar a uno en la mscara de eventos. Se deben tener en cuenta todas las consideraciones
que se establecieron para la funcin 000Ch.
Al controlador de eventos se le pasan los mismos valores que para el caso del controlador
instalado mediante la funcin 000Ch (teniendo en cuenta que la asignacin de bits en AX coincide
con la de CX).
Esta funcin le permite a un programa instalar un mximo de tres controladores de eventos
alternativos diferentes.

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
*/
/*************************/

0x8000, 0xE000, 0xF800, 0xFE00,


0xD800, 0x0C00, 0x0600, 0x0300,
0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000,

/*************************/
/*
Mascara de
*/
/*
cursor
*/
/*************************/

};
rin.h.ah = 0;
rin.h.al = 6;
int86 (0x10, &rin, &rout);

/* Pone la pantalla en modo grafico */

rin.x.ax = 0;
int86 (0x33, &rin, &rout);

/* Reinicia el raton */

rin.x.ax = 1;
int86 (0x33, &rin, &rout);

/* Muestra el cursor del raton (por defecto) */

rin.x.ax = 9;
rin.x.bx = 0;
rin.x.dx = (unsigned int) tab;
int86 (0x33, &rin, &rout);

/* Crea un nuevo cursor */

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:

- Seleccionar el modo de video


Entrada: AH = 0
AL = modo (los de la tabla de abajo).
Salida: No tiene

- Averiguar el modo de video actual


Entrada: AH = Fh
Salida: AL = modo actual
AH = nmero de columnas (slo en los modos de texto)
BH = pgina actual
- Fijar el tamao del cursor en modo texto
Entrada: AH = 1
CH = nmero de lnea inicial
CL = nmero de lnea final
Salida: No tiene
Nota: Usando esta funcin es posible modificar el tamao del cursor en pantalla e incluso hacerlo
desaparecer, asignando valores adecuados a los registros ch y cl .
- Colocar el cursor
Entrada: AH = 2
DH = nmero de fila
DL = nmero de columna
BH = nmero de pgina
Salida: No tiene
Nota: Sita el cursor en modo texto en la posicin determinada por los registros dh y dl.

- Escribir un carcter en pantalla


Entrada: AH = 9
AL = cdigo ASCII del carcter
BL = color
BH = nmero de pgina
CX = nmero de repeticiones
Salida: No tiene
Nota: Escribe un carcter en la posicin actual del cursor. En cuanto al byte del color, el primer
nibble fija el color de fondo y el segundo nibble el color del carcter.

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.

Acceso al CD a travs del controlador de dispositivos


La forma ms sencilla de acceder al controlador de dispositivos es a travs de la funcin
AX=1510h de la interrupcin 2Fh que se acaba de describir. Tal como se ha mencionado
anteriormente, esta funcin usa un bfer para transferir los parmetros y obtener los resultados,
cuya direccin se pasa a travs de los registros ES y BX.
No obstante, la estructura de este buffer difiere segn el tipo de subfuncin que se desee
ejecutar puesto que el nmero de parmetros es distinto en cada caso.
A continuacin se describen las distintas subfunciones a las que se puede acceder a travs
de la funcin 1510h, y la estructura adecuada para el bfer correspondiente (nota: los campos no
descritos se pueden rellenar con 0s).
Subfuncin 84h: PLAY AUDIO (Reproduccin de Audio)

Significado de la palabra de estado (16 bits):


bit 15 = 0: No hubo error
= 1: Hubo un error. Cdigo de error en los bits 7..0
bits 7..0

bit 9 = 1: Controlador ocupado con la reproduccin de una pista de audio

Subfuncin 85h: STOP AUDIO (Detencin de la reproduccin de Audio)

Subfuncin 88h: RESUME AUDIO (Continuar la reproduccin de Audio)


Mediante esta subfuncin se puede retomar la reproduccin de una pista tras haber sido
interrumpida mediante STOP AUDIO.

Subfuncin 03: IOCTL INPUT (Solicitud de informacin al controlador):


Esta subfuncin permite solicitar al controlador de CD diversas informaciones importantes
relacionadas con el estado de reproduccin del CD, el nmero de pistas, donde comienza cada
pista, etc.
Para reconocer el tipo de peticin se debe pasar la direccin de un segundo buffer (buffer
secundario), cuya estructura difiere segn el tipo de informacin requerida.

En la siguiente tabla se describen algunas de las solicitudes que pueden realizarse


mediante esta subfuncin, junto con el tamao del buffer secundario correspondiente (el valor que
habra que poner en el offset 12h del buffer principal):

A continuacin se describe la estructura de los buffers secundarios correspondientes a


cada una de las solicitudes previamente mencionadas:
Solicitud 06h: Estado de la unidad
Estructura del buffer secundario

Los bits ms importantes de los 32 que devuelve esta funcin son:


bit 0

= 1: Unidad abierta
= 0: Unidad cerrada

bit 3

= 0: Unidad slo puede leer


= 1: Unidad puede leer y escribir

bit 4

= 0: Unidad slo soporta CDs de datos


= 1: Unidad soporta CDs de datos y de audio

bit 11

= 0: Se encuentra un CD insertado en la unidad


= 1: No hay CD insertado en la unidad

Solicitud 08h: Tamao del CD


Estructura del buffer secundario

Solicitud 09h: Informacin sobre cambio de disco


Estructura del buffer secundario

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

Solicitud 0Ah: Informacin sobre el nmero de pistas del CD


Estructura del buffer secundario:

Solicitud 0Bh: Informacin sobre una pista concreta del CD


Estructura del buffer secundario

En el campo de informacin sobre el tipo de pista, lo ms importante es el bit 6:


bit 6

= 0 pista de audio
= 1 pista de datos

Solicitud 0Ch: Posicin de la cabeza lectora durante la reproduccin del CD


Mediante esta solicitud podemos saber qu pista se est reproduciendo
actualmente y en qu posicin se encuentra la cabeza lectora con respecto a la pista actual
y con respecto al tiempo de reproduccin total.

Estructura del buffer secundario

Solicitud 0Fh: Estado de reproduccin del CD


Estructura del buffer de paso

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;

/*VGA 320x200 - 256 colores*/

int86 (0x10, &regs, &regs); /*cambiar modo de vdeo*/


...

La diferencia entre int86() e int86x()


reside en que la ltima permite trabajar
con los registros de segmento.
La estructura SREGS se puede
inicializar con los valores que tienen que
tener los registros de segmento antes de
llamar a la interrupcin.

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

Otros mtodos de interrupciones:


void enable(void);
void disable(void);

/* habilitar interrupciones hardware */


/* inhibir interrupciones hardware */

Cambio de vectores de interrupcin:


void interrupt (*getvect(int interrupcin))( );
void setvect (int interrupcin, void interrupt (*rutina)( ));
La funcin getvect() devuelve un puntero con la direccin del vector de interrupcin
indicado. La funcin setvect() permite desviar un vector hacia la rutina de tipo interrupt que se
indica. Interrupt es una palabra clave de C que ser explicada en el futuro. Por ahora, baste el
siguiente programa de ejemplo:

void interrupt nueva_rutina( );


void interrupt (*vieja_rutina)( );
int main( )
{
vieja_rutina = getvect (5);
setvect (5, nueva_rutina);
...
...
...
setvect (5, vieja_rutina);
}
void interrupt nueva_rutina( )
{
...
}

/* nuestra funcin de interrupcin */


/* variable para almacenar el vector inicial */

/* almacenar direccin de INT 5 (activada con Print Screen) */


/* desviar INT 5 a nuestra propia rutina de control */
/* resto del programa */
/* restaurar rutina inicial de INT 5 */
/* rutina de control de INT 5 */

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

/* devuelve la versin del DOS de manera completa */


/* devuelve el nmero principal de versin del DOS: ej., 5 en el DOS 5.0 */
/* devuelve el nmero secundario de versin del DOS: ej., 0 en el DOS 5.0 */
/* segmento del PSP */
/* contiene el tamao de la pila, en bytes */
/* almacena el tamao inicial del heap, en bytes (0 para maximizarlo) */

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.

Es a partir del Borland C++ cuando se


puede trabajar a gusto: en concreto, la versin
Borland C++ 2.0 permite ensamblar sin rodeos
cdigo ensamblador incluido dentro del listado
C.
Las versiones 3.x en adelante de este
compilador son las ms adecuadas para un
486 o superior (bajo DOS). Yo uso Borland C++
4.02. La sintaxis de asm se puede entender
fcilmente con un ejemplo:
Como se ve en el ejemplo, los registros
utilizados son convenientemente preservados
para no alterar el valor que puedan tener en
ese momento (importante para el compilador).
Tambin puede observarse lo fcil que
resulta acceder a las variables. Cuidado con
BP: el registro BP es empleado mucho por el
compilador y no conviene tocarlo (ni siquiera
guardndolo en la pila).
Por ejemplo, la instruccin del
programa MOV CX,DATO1 ser compilada
como MOV CX,[BP-algo] al ser una variable
local de main().

#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.

Funciones para acceder al ms bajo nivel (integrar el ensamblador y el C).


Acceso a los puertos de entrada / salida:
int inp (int puerto);
/* leer del puerto E/S una palabra (16 bits) */
int inport (int puerto);
/* leer del puerto E/S una palabra (16 bits) */
unsigned char inportb (int puerto);
/* leer del puerto E/S un byte (8 bits) */
int outp (int puerto, int valor);
/* enviar al puerto E/S una palabra (16 bits) */
void outport (int puerto, int valor);
/* enviar al puerto E/S una palabra (16 bits) */
void outportb (int puerto, unsigned char valor); /* enviar al puerto E/S un byte (8 bits) */
Aunque pueden parecer demasiadas, algunas son idnticas (caso de inp( ) e inport( )) y
otras se diferencian slo ligeramente en el tipo de los datos devueltos, lo cual es irrelevante si se
tiene en cuenta que el dato devuelto es descartado (caso de outp( ) y outport( )).
En general, lo normal es emplear inport( ) e inportb( ) para la entrada, as como outport( ) y
outportb( ) para la salida. Por ejemplo, para enviar el EOI al final de una interrupcin hardware se
puede ejecutar: outportb(0x20, 0x20);

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);

/* leer la palabra (16 bits) en seg:off */


/* leer el byte (8 bits) en seg:off */
/* poner palabra valor (16 bits) en seg:off */
/* poner byte valor (8 bits) en seg:off */
/* obtener offset de variable tipo far */
/* obtener segmento de variable tipo far */
/* convertir seg:off en puntero tipo far */

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;

Listado completo de interrupciones


INT 00:
INT 01:
INT 02:
INT 03:
INT 04:
INT 05:
INT 06:
INT 07:
INT 08:
INT 09:
INT 0A:
INT 0B:
INT 0C:
INT 0D:
INT 0E:
INT 0F:
INT 10:
INT 11:
INT 12:
INT 13:
INT 14:
INT 15:
INT 16:
INT 17:
INT 18:
INT 19:
INT 1A:
INT 1B:
INT 1C:
INT 1D:
INT 1E:
INT 1F:
INT 20:
INT 21:
INT 22:
INT 23:
INT 24:
INT 25:
INT 26:
INT 27:
INT 28:
INT 29:
INT 2A:
INT 2B-2D:
INT 2E:
INT 2F:
INT 30:
INT 31:
INT 32:
INT 33:
INT 34-3F:
INT 40:
INT 41:
INT 42:

Divisin por cero


Ejecucin paso a paso
No Enmascarable (NMI)
Puntos de ruptura
Desbordamiento (INTO)
Volcar pantalla por impresora (BIOS)
Cdigo de operacin incorrecto
Reservada
IRQ 0: Contador de hora del sistema (BIOS)
IRQ 1: Interrupcin de teclado (BIOS)
IRQ 2: canal E / S, segundo 8259 del AT
IRQ 3: COM2
IRQ 4: COM1
IRQ 5: disco duro XT, LPT2 en AT, retrazo vertical PCjr
IRQ 6: Controlador del disquete
IRQ 7: LPT1
Servicios de vdeo (BIOS)
Listado del equipo (BIOS)
Tamao de memoria (BIOS)
Servicios de disco (BIOS)
Comunicaciones en serie (BIOS)
Servicios del sistema (BIOS)
Servicios de teclado (BIOS)
Servicios de impresora (BIOS)
IBM Basic (ROM del BASIC)
Arranque del sistema (BIOS)
Fecha / hora del sistema
Accin de CTRL-BREAK (BIOS)
Proceso peridico del usuario (Usuario)
Parmetros de vdeo (BIOS)
Parmetros del disquete (BIOS)
Tabla de caracteres grficos (BIOS)
Fin de programa (DOS)
Servicio del sistema operativo (DOS)
Direccin de terminacin (DOS)
DOS CTRL-BREAK (DOS)
Manipulador de errores crticos (DOS)
Lectura absoluta de disco (DOS)
Escritura absoluta en disco (DOS)
Terminar permaneciendo residente (DOS)
DOS Idle (programas residentes que usan funciones DOS)
DOS TTY (impresin en pantalla)
Red local MS net
Uso interno del DOS
Procesos Batch (DOS)
Multiplex (DOS)
Compatibilidad CP/M-80 (xx:YYyy en JMP XXxx:YYyy)
Compatibilidad CP/M-80 (XX en JMP XXxx:YYyy)
Reservada
Controlador del ratn
Reservadas
Interrupcin de disquete (BIOS)
Parmetros del disco duro 1 (BIOS)
Apunta a la INT 10h original del BIOS si existe VGA

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:

Caracteres grficos EGA (BIOS)


Reservadas
Parmetros del disco duro 2 (BIOS)
Reservadas
Alarma del usuario
Reservadas
Para uso de los programas
Interrupcin de EMS (controlador EMS)
Reservadas
IRQ 8: Reloj de tiempo real AT (2 chip 8259-AT)
IRQ 9: IRQ 2 redireccionada (2 chip 8259-AT)
IRQ 10: reservada (2 chip 8259-AT)
IRQ 11: reservada (2 chip 8259-AT)
IRQ 12: interrupcin de ratn IBM (2 chip 8259-AT)
IRQ 13: error de coprocesador matemtico (2 chip 8259-AT)
IRQ 14: controlador disco fijo (2 chip 8259-AT)
IRQ 15: reservada (2 chip 8259-AT)
Reservadas
Reservadas para el Basic
Usadas por el Basic
Para uso de los programas

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

-gmodo: especifica el modo de vdeo


que van a usar las funciones de
grficos.

initgraph(&gdriver, &gmodo, "C:\\BC4\\BGI");


/* lnea desde el pxel (100,100) hasta el (400, 400) */
line(100, 100, 400, 400);
getch( );
closegraph( );

/* "cerrar" los grficos */

puts("Ahora he vuelto al modo de texto,");


puts("pa recordar viejos tiempos...");
getch( );
}

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);

La funcin initgraph inicializa el modo


grfico usando esos valores y el path de
nuestros archivos BGI.

initgraph(&tarjeta, &modo," C:\\BC4\\BGI");

En la variable error volcamos el


resultado de llamar a la funcin graphresult,
para ver el estado de nuestro modo grfico. Si
es distinto de 0, imprimimos en pantalla el
mensaje de error devuelto por la funcin
grapherrormsg envindole el valor que tenga la
variable error, y si es 0, nuestro modo grfico
est bien, esperamos que se pulse una tecla y
cerramos el modo grfico.

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.

Funciones de dibujo: un listado de las funciones de C para pintar la pantalla (<graphics.h>):


getmaxx ( ): Retorna la coordenada mxima horizontal (probablemente 639). Si por ejemplo
estuviramos trabajando con una resolucin de 320 x 200, esta funcin devolvera el valor
319, ya que empieza a contar por el 0.
getmaxy ( ): Retorna la coordenada mxima vertical (probablemente 479). Es como la anterior,
pero para la otra coordenada.
setcolor ( color ); cambia el color actual. Los colores en mi Borland C++ son:
0
1
2
3
4
5
6
7
Ejemplos:










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( color ): Selecciona el color de fondo indicado.


Ejemplos:

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

cleardevice ( ): Borra la pantalla. Es como el clrscr( ) del modo texto.


clearviewport ( ): borra la pantalla y la pone toda del color actual.
getx ( ): Retorna la coordenada actual horizontal. Ejemplo: hact = getx( );
gety ( ): Retorna la coordenada actual vertical. Ejemplo: vact = gety( );
moveto (x , y): Se mueve a las coordenadas indicadas. Ejemplo: moveto(320,240);
getcolor ( ): Retorna el color de dibujo y texto actual. Ejemplo: coloract = getcolor( );
getbkcolor ( ): Retorna el color de fondo actual. Ejemplo: fondoact = getbkcolor( );
getimage (x, y, x, y, array): guarda en la cadena especificada como quinto parmetro la
imagen que haya en pantalla desde las coordenadas (x, y) hasta (x, y).
putimage (x, y, array, operacin): dibuja a partir de las coordenadas (x, y) la imagen guardada
en array. La imagen puede estar predefinida o ya capturada con getimage( ).
floodfill (x, y, color_borde): rellena la figura cerrada por el color color_borde con el color actual
desde el punto (x, y).

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

bar (x, y, x, y): Dibuja una barra ( rectngulo ) y si es posible la


rellena.

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

8x8 bit-mapped font


Stroked triplex font
Stroked small font
Stroked sans-serif font
Stroked gothic font
Stroked script font
Stroked triplex script font
Stroked triplex script font
Stroked complex font
Stroked European font
Stroked bold font

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

Algunos ejemplos prcticos: aqu me he currado (y pillado de Internet) algunos ejemplos de


programas en modo grfico. A mi me funcionan correctamente. No olvides crear el ejecutable como
proyecto y dems.
Comprobar los colores que podemos
usar
En este programa, tras iniciar el
modo grfico, me introduzco en un bucle
que ir mostrando uno a uno, todos los
colores de que disponemos.
Como por norma general los
colores definidos son quince, el bucle
acabar al alcanzar esta cifra.
Con la funcin setbkcolor, lo que
hacemos es rellenar toda la pantalla del
color que indique la variable contador, y
sacamos con una printf el nmero del
color para que el usuario lo vea.

Poner una palabra encuadrada


Tras lo de iniciar grficos, escojo
el estilo de texto para que la palabra nos
quede ms majica.
Luego saco ms o menos
centrada la palabra con la funcin
outtextxy.

#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");

Despus dibujo un rectngulo


para enmarcar nuestro mural informtico.
Por ltimo le digo al ordenador
que se eche una siesta de unos dos
segundos (y va que arde), para despus
proceder a cerrar el modo grfico.

settextstyle(4,0,5);
outtextxy(220,200,"MARTIN");
rectangle(100,100,530,370);
sleep(2);
closegraph( );
}

Dibujar una grfica de


una funcin matemtica
Este
programa
evidentemente no es
mo, pero tampoco s
quin lo program, por lo
cual elogio su paciencia
para poner el pedazo de
frmula que dibuja la
funcin (sen x) / x.
Gracias.
La grfica se
dibuja iluminando puntos
sobre la pantalla, gracias
al bucle for se rellena
toda la pantalla.
Suponemos claro
que el ancho de pantalla
sea de 640 puntos.
Para ello se ha
amplificado la grfica de
la funcin y desplazado
los ejes para ajustarlos a
los puntos fsicos de la
pantalla

#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.

outtextxy(0, 0, "* <-- (0, 0) viewport por defecto");


setviewport(50, 50, getmaxx()-50, getmaxy()-50, 1);
outtextxy(0, 0, "* <-- (0, 0) mi viewport");
getch( );
closegraph( );
}

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);

Una vez aadidos todos los archivos,


deberemos editar el cdigo fuente donde
inicializamos el modo grfico, y aadir las
siguientes lneas:

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.

Anexo: ratn en modo grfico


Esto va para aquellos que vayan a hacer un programa el que que quieran usar ratn. La
posicin que ocupa el ratn en la pantalla se define siempre en coordenadas graficas, aunque se
este trabajando en modo texto, considerando tantos puntos en la pantalla como permita la tarjeta
grafica
El desplazamiento del cursor se realiza mediante una unidad de movimiento del ratn que
recibe el nombre de mickey y equivale a 1/200 pulgadas. Cuando se mueve el ratn, el controlador
del ratn (el programa driver) mueve el cursor horizontal y verticalmente un numero de pxeles que
depende de la sensibilidad del ratn. Por defecto, cuando se instala el controlador del ratn, para
conseguir un desplazamiento de 8 pxeles se precisan 8 mickeys en horizontal y 16 en vertical. EI
control del ratn se realizaba mediante la interrupcin int 0x33 (debes haber visto el tema anterior).
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include <stdlib.h>
#include <graphics.h>
main( )
{
union REGS estado ;
int driver, mode;
detectgraph(&driver, &mode);
initgraph(&driver, &mode, "C:\\BC4\\BGI");
estado.x.ax = 0;
int86 (0x33, &estado, &estado) ;

/* Mira si hay raton instalado */

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 */

printf("Coordenadas del raton: ");


printf("\nPulsa tecla para terminar.") ;
while (!kbhit( ))
{
gotoxy(24,1) ;
estado.x.ax = 3;
/* Obtiene las coordenadas del ratn */
int86 (0x33, &estado, &estado) ;
printf ("%3i,%3i" , estado.x.cx, estado.x.dx) ;
}
closegraph( ) ;
estado.x.ax = 2;
int86 (0x33, &estado, &estado) ;
}

/* cierra el modo grafico */


/* Quita el cursor del ratn */

Fin

Y se acab. Yo creo que para empezar a programar est


bastante bien. Espero que sea de utilidad todo esto, que si
no, menuda prdida de tiempo he estado haciendo...
Linares, a 19 de Julio de 2002

You might also like