Documentos de Académico
Documentos de Profesional
Documentos de Cultura
FUNDAMENTOS DE PROGRAMACIÓN
Tema 4
Comenzando a programar
__________________________________________________________________________________________________________
Comenzando a programar 1
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
- La principal ventaja del diseño modular es que si un trozo de código -que realiza
una tarea determinada- se repite a lo largo del programa, es más cómodo y
económico implementarlo una sola vez, como una función, que podremos
utilizar en diferentes situaciones y localizaciones del programa, cada vez que se
necesite, con sólo escribir el nombre de la función y sin necesidad de repetir las
mismas líneas cada vez.
- Incluso, si la función es lo suficientemente general, se podrá utilizar en
diferentes programas.
- De todos modos, aunque la porción de programa que realiza una cierta tarea se
haya de emplear una sola vez a lo largo del mismo, es conveniente
implementarlo como una función ya que los programas modulares son más
fáciles de leer, de depurar y de mantener.
- Al utilizar nombres descriptivos (significativos) para las funciones queda más
claro cómo está organizado el programa.
- Además, cada función se puede afinar por separado hasta conseguir que haga lo
que se pretenda de ella.
- Las funciones se pueden considerar como "cajas negras", definidas
exclusivamente por la información que hay que suministrarles (su entrada) y el
producto que devuelven (su salida). De esta manera, podremos interesarnos sólo
por el diseño global del programa dejando para más tarde el resolver los detalles.
.....
void main(void)
{
leer_datos (...);
calcular_area (...);
ver_resultado (...);
}
void leer_datos (....)
{
......
}
void calcular_area(....)
{
......
}
void ver_resultado (....)
{
......
}
__________________________________________________________________________________________________________
Comenzando a programar 2
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
- Las funciones usadas por main() hay que declararlas antes de ésta. Esta
declaración previa de cada función se conoce como declaración del prototipo de
función que es usado por el compilador para comprobar que cada vez que se llama a
esa función se le mandan los argumentos en número y tipo correctos y que el valor
retornado se trata correctamente. Cada declaración de prototipo debe coincidir con la
correspondiente definición de función, excepto que no es necesario precisar los
identificadores de los argumentos, si los hubiere, y que detrás de los paréntesis SÍ se
coloca un punto y coma.
- Para llamar a una función (para que se ejecute) se escribe su nombre seguido
por paréntesis (dentro de éstos los posibles parámetros actuales) y detrás un punto y
coma con el fin de crear una sentencia.
#include <string.h>
#include <conio.h>
#define NOMBRE "ORDENATAS,S.A."
#define DIRECC "Plaza del Byte, 16"
#define CIUDAD "08008 Villabits"
// Prototipos de las funciones:
void asteriscos(void);
void espacios(int); // La función espacios es llamada 3 veces,
void main(void) // utilizando como argumento efectivo:
{ // 1º:una constante
int salta; // 2º:una variable
asteriscos(); // 3º:una expresión
espacios(33); // Como 14 es la longitud de NOMBRE,
printf ("%s\n",NOMBRE); // (80 - 14) / 2 da como resultado
33
salta = (80-strlen(DIRECC))/2; //strlen : longitud de una cadena
espacios(salta);
printf ("%s\n",DIRECC);
espacios ( ( 80 - strlen (CIUDAD) ) /2 );
printf("%s\n", CIUDAD);
asteriscos();
getch();
}
// Declaración de funciones
void asteriscos(void)
{
int cont;
for (cont=1; cont <= 80; cont++)
putchar('*');
}
Ya hemos comentado que una función puede apoyarse en otra u otras para
realizar la tarea que tiene encomendada. Y es frecuente que cuando una función f1()
__________________________________________________________________________________________________________
Comenzando a programar 4
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
#include <stdio.h>
#include <string.h>
#include <conio.h>
#define MAX_OPC 3
#define MAX_ALU 10
int menu(int);
float leer_notas(int);
float calcular_media (float, int);
void main(void)
{
int opcion=0, hay_datos = 0;
float total= 0.0, media=0.0, max_media=0.0;
while (opcion != MAX_OPC)
{
opcion = menu(MAX_OPC);
switch (opcion)
{
case 1: total = leer_notas(MAX_ALU);
hay_datos = 1;
break;
case 2: if (hay_datos)
{
media = calcular_media(total, MAX_ALU);
if (media > max_media)
max_media = media;
}
}
}
printf("\n\nLa maxima media obtenida ha sido %.2f ", max_media );
getch();
}
//*****************************************************************
int menu(int tope_op)
{
int opcion_menu;
__________________________________________________________________________________________________________
Comenzando a programar 5
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
clrscr();
printf("\t1. Introducir notas\n");
printf("\t2. Calcular media.\n");
printf("\t3. Finalizar.\n");
do
{
printf("\n\n\tElija opcion: ");
scanf("%d", &opcion_menu);
fflush(stdin);
}
while ( opcion_menu < 1 || opcion_menu > tope_op);
return opcion_menu;
}
//*****************************************************************
float leer_notas(int tope)
{
int i;
float total=0.0 , nota;
clrscr();
printf("Introduzca las notas (valores reales 0-10). \n\n");
for (i=1; i <= tope ; i++)
{
do
{
printf("Nota %d) ",i);
scanf("%f", ¬a);
}
while (nota < 0.0 || nota > 10.0);
total += nota;
}
printf("\n\nPulse una tecla para volver al menu.");
getch();
return total;
}
//******************************************************************
float calcular_media (float total_notas, int tope)
{
clrscr();
printf("Calculando la media de las notas de %d alumnos.\n", MAX_ALU);
printf("Total alumnos = %d\n", MAX_ALU);
printf("Total puntos = %.2f\n", total_notas);
printf("Media aritm. = %.2f\n", total_notas/tope);
printf("\n\nPulse una tecla para volver al menu.");
getch();
return total_notas/tope;
}
4. LA INSTRUCCIÓN return
__________________________________________________________________________________________________________
Comenzando a programar 6
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
opcion = menu(MAX_OPC);
total = leer_notas(MAX_ALU);
media = calcular_media(total, MAX_ALU);
Sin embargo, hubiera sido perfectamente válido que una sentencia de la función
principal fuese así:
El tipo del valor que se retorna debe coincidir con el que se escribe delante del
nombre de la función cuando se declara el prototipo de la función (que a su vez debe
ser idéntico al que se antepone al nombre de la función en su definición).
Así, en el programa anterior se usan 3 funciones, cuyos prototipos son los
siguientes:
return;
#include <stdio.h>
#include <conio.h>
unsigned abs(int); // Prototipo de la función abs
void main(void)
{
int a=10, b=0, c=-22;
int d,e,f, result;
d = abs(a);
e = abs(b);
f = abs(c);
result = d + 5 * abs(c-a);
printf (" %d %d %d %d %d %d", d, e, f, abs(-3), abs(3), result );
getch();
}
//*********** Función valor absoluto. Definición
unsigned abs(int x)
__________________________________________________________________________________________________________
Comenzando a programar 7
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
{
unsigned y;
y = (x<0)? -x : x;
return (y);
}
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#define TOPE_NUM 1000
#define MAX_OPORT 5
int pide_numero (int, int);
int suma_dig (int);
void main(void)
{
int num_aleat, intentos = 0, adivina = 0 ;
clrscr();
randomize();
printf ("Adivine un numero entre 1 y %d.\n", TOPE_NUM);
printf ("Tiene %d oportunidades\n", MAX_OPORT);
num_aleat = 1 + rand() % TOPE_NUM;
printf("AYUDA : Sus digitos suman %d\n\n", suma_dig ( num_aleat) );
while (intentos < MAX_OPORT && !adivina )
{
intentos++ ;
adivina = pide_numero (intentos, num_aleat);
}
if (!adivina)
printf("Lo siento. El numero a adivinar era %d \n", num_aleat );
getch();
}
//******************************
int suma_dig ( int aleat)
{
int total = 0;
while ( aleat )
{
total += aleat % 10;
aleat /= 10;
}
return total;
}
//******************************
int pide_numero ( int intentos, int aleat)
{
int numero;
do
{
printf("Intento numero %d = ", intentos);
scanf("%d", &numero);
}
while (numero < 1 || numero > TOPE_NUM);
if (numero == aleat)
{
printf("ENHORABUENA.\n\n");
return 1;
} // else no hace falta, aunque sería más correcto utilizarlo
if (numero < aleat)
printf("El numero es mayor que el introducido.\n\n");
else
printf("El numero es menor que el introducido.\n\n");
return 0;
}
__________________________________________________________________________________________________________
Comenzando a programar 8
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
5. PUNTEROS.
• El operador &
ptr_int1 = &nieve;
ptr_int2 = &bola;
#include <stdio.h>
#include <conio.h>
void f2(int);
void main(void)
{
int x=24,y=5;
printf ("En main(), x=%d y su dirección es %p\n", x, &x);
//Resultado: x=24 Dirección=6618624
printf ("En main(), y=%d y su dirección es %p\n", y, &y);
//Resultado: y=5 Dirección=6618620
f2(x);
getch();
}
//**********************************************************
void f2(int y) // parámetro pasado por valor
{ // y -variable local- recibe el valor de la x de main()
__________________________________________________________________________________________________________
Comenzando a programar 9
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
Por ejemplo:
Se dice que la variable ptr_int1 (al igual que ptr_int2) es un puntero de tipo int,
lo que significa que podrá contener la dirección de cualquier variable de tipo entero.
Sea el caso de la variable x de tipo int, se podrá realizar la asignación...
ptr_int1 = &x ;
ptr_int1 = &z ;
Ahora “ptr_int1 apunta a la variable z”. Y de este modo, cuantas veces sea
preciso.
- Por otra parte, también es fácil conseguir que una variable puntero apunte al
mismo lugar que otra:
ptr_int2 = ptr_int1 ;
Lo habitual es que una variable puntero apunte a variables del mismo tipo que el
tipo que se ha declarado para ella.
__________________________________________________________________________________________________________
Comenzando a programar 10
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
• El operador *
var_fl = 23.56;
ptr_fl = &var_fl;
#include <stdio.h>
#include <conio.h>
void main(void)
{
int x=5;
int y;
int *dir; //Declaración de puntero: dir es puntero de tipo int
dir = &x; // & es operador de dirección, dir "apunta a" x
printf("Valor de x = %d, su dirección = %p\n", x, &x);
// Resultado: Valor=5, Dirección=6618624
//El operador de indirección * seguido por un puntero da el
//valor almacenado en la dirección apuntada por el mismo
printf("Valor de x = %d, su dirección = %p\n", *dir, dir);
// Resultado: Valor=5, Dirección=6618624
y = *dir + 3;
printf ("Valor de y = %d.\n", y); // Resultado: y=8
dir = &y; // Ahora dir apunta a y
printf("Dirección de y = %p \n", dir); // Resultado: Dirección y=6618620
printf("Valor y=Contenido posicion apuntada por dir es %d\n", *dir);
getch(); // Resultado: Valor y = 8
}
#include <stdio.h>
#include <conio.h> //Ahora si funciona bien INTERCAMBIA
void intercambia(int *,int *);
void main(void)
{
int x=5, y=10;
printf ("En principio x=%d, y=%d.\n", x, y); //x=5, y=10
intercambia ( &x, &y);
printf ("Ahora x=%d, y=%d.\n", x, y); //x=10, y=5
getch();
}
{
int aux;
aux = *u;
*u = *v;
*v = aux;
}
#include <stdio.h>
#include <conio.h>
void incrementa(int *);
void main(void)
{
int x=150;
printf("En función principal, x = %d.\n",x); // x=150
incrementa(&x);
printf("Función incrementa() cambia su valor: x = %d\n",x); // x=160
getch();
}
#include <stdio.h>
#include <conio.h>
void intercambia(int,int);
void main(void)
{
// NO SE INTERCAMBIAN LOS VALORES DE LAS VARIABLES
int x=5, y=10;
printf("En principio x= %d, y= %d.\n", x, y); // x=5, y=10
intercambia (x, y);
printf("Ahora x= %d, y= %d.\n", x, y); // x=5, y=10
getch();
}
//***********************************************************
// Aunque en la función INTERCAMBIA los parámetros formales
// se llamen X e Y en lugar de U y V, los valores no se intercambiarán
// en la función que realiza la llamada.
//***********************************************************
void intercambia(int u,int v)
{
int aux; //Los valores sólo se intercambian dentro de esta función.
// Antes de intercambiar: u=5, v=10
aux = u;
u = v;
v = aux;
} // En este momento: u=10, v=5
__________________________________________________________________________________________________________
Comenzando a programar 12
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
intercambia(&x,&y);
#include <stdio.h>
#include <conio.h> //Ahora si funciona bien INTERCAMBIA
void intercambia(int *,int *);
void main(void)
{
int x=5, y=10;
printf ("En principio x=%d, y=%d.\n", x, y); //x=5, y=10
intercambia ( &x, &y);
printf ("Ahora x=%d, y=%d.\n", x, y); //x=10, y=5
getch();
}
#include <stdio.h>
#include <conio.h>
void incrementa(int *);
void main(void)
{
int x=150;
printf("En función principal, x = %d.\n",x); // x=150
incrementa(&x);
printf("Función incrementa() cambia su valor: x = %d\n",x); // x=160
getch();
}
__________________________________________________________________________________________________________
Comenzando a programar 13
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
7. MODOS DE ALMACENAMIENTO.
• auto
Todas las variables declaradas en una función son, por defecto, automáticas; es
decir, que lo son si no se utiliza ninguna palabra clave. Opcionalmente, se puede utilizar
la palabra auto para declararlas. Los argumentos formales son necesariamente variables
automáticas.
Las variables automáticas tienen alcance local, o sea, sólo son conocidas en la
función donde se han definido. Cuando la función acaba su tarea, las posiciones de
memoria empleadas para sus variables locales se emplearán para otros usos. Por ello,
está permitido emplear los mismos identificadores para variables diferentes en distintas
funciones.
• extern
Cuando una variable se define fuera de una función se dice que es externa. Dicha
variable externa puede ser declarada dentro de la función que la emplea utilizando la
palabra clave extern: esta palabra clave informa al ordenador que debe buscar la
definición de la variable fuera de la función.
Las variables externas tienen alcance global y permanecen en memoria durante
toda la ejecución del programa, ya que al no pertenecer a ninguna función en concreto
no pueden eliminarse al acabar ninguna de ellas. Nota: extern int equivale a extern.
#include <stdio.h>
#include <conio.h>
int cont=10; //cont es una variable externa
void mensaje1(void);
void mensaje2(void);
void main(void)
{ int i;
extern minimo; //declaración de minimo
printf("Cuenta por lo menos hasta %d.\n\n", minimo);
for (i=1; i <= cont ; i++)
printf("Cuento %d.\n",i);
mensaje1();
mensaje2();
__________________________________________________________________________________________________________
Comenzando a programar 14
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
getch();
}//************************************************************
void mensaje1(void) //Aqui se ignora la var. global cont puesto que se
define
{ //una variable local con ese mismo nombre.
auto int cont = 7; // si se omite auto funciona igual
printf("\nEs verdad que ha contado hasta %d ? \n",cont);
printf("No es verdad...");
}
//************************************************************
int minimo = 5; //definición de la variable externa minimo
void mensaje2(void)
{
printf("...En realidad ha contado hasta %d.\n",cont);
}
- Definiciones y declaraciones.
declarado la variable local hacen referencia a la variable local y no tienen efecto sobre
la variable global.
El almacenamiento de las variables globales tiene lugar en una región de
memoria fija definida para este propósito por el compilador. Las variables globales son
muy útiles cuando se utilizan los mismos datos en muchas funciones del programa. Sin
embargo, se debería evitar el uso innecesario de variables globales por tres razones:
Por otra parte, las variables locales son variables que sólo son conocidas por las
funciones que las usan. Incluso en el caso de usar el mismo nombre de variable en
distintas funciones, el ordenador es capaz de distinguirlas puesto que tienen un ámbito
distinto y por tanto, a todos los efectos, son diferentes.
Si en un programa no queremos utilizar variables globales y necesitamos
comunicar valores entre las distintas funciones hay que utilizar argumentos y return. Es
decir, una función podrá recibir todos los valores que necesite a través de los parámetros
formales, y podrá devolver un valor a la función que la ha llamado a través de la
expresión que se coloca detrás de return. El único problema aquí es que a través de
return sólo puede retornar 1 valor. Veremos más adelante cómo salvar esta dificultad
mediante el empleo de punteros.
• static
#include <stdio.h>
#include <conio.h>
int tope_preguntas = 3;
int num_pregunta = 0; //En general, no es aconsejable utilizar var. globales
void soy_un_mandao(void);
__________________________________________________________________________________________________________
Comenzando a programar 16
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
void main(void)
{
int i;
for (i=1; i <= tope_preguntas ; i++)
{
printf("(Pregunta %d) Cuanto vale un peine?\n", ++num_pregunta );
soy_un_mandao();
printf("\n\n...Y con la rebaja,...\n");
}
getch();
}
//**********************************************
void soy_un_mandao(void)
{ // no es imprescindible la declaración:
extern int num_pregunta;
static unsigned precio = 100;
printf("\nHoy vale %d. Ya me lo ha preguntado %d %s.\n",precio++,
num_pregunta, num_pregunta>1 ? "veces": "vez" );
}
#include <stdio.h>
#include <conio.h>
int cuenta (int i);
void main(void)
{
do
{
cuenta(0);
}
while (!kbhit()); //kbhit devuelve 0 si no se ha pulsado ninguna tecla
printf("Se ha pulsado la tecla %c\n\n", getch());
printf("Funcion cuenta() ha sido llamada %d veces", cuenta(1));
getch();
}
int cuenta (int i)
{
static int c=0;
if (i)
return c;
else
c++;
return 0;
}
partir del último. Se puede declarar una variable global para este valor. Sin embargo,
cada vez que se utiliza la función en un programa, tendríamos que acordarnos de
declarar dicha variable global y asegurarnos que no entra en conflicto con cualquier otra
variable global ya declarada, un gran inconveniente. También, la utilización de una
variable global haría difícil colocar esta función en una biblioteca de funciones. La
mejor solución es declarar como static la variable que almacena el número generado,
como en este fragmento de programa:
En este ejemplo, la variable azar mantiene su valor entre las llamadas la función,
en lugar de crearse e inicializarse como ocurriría con una variable local normal. Esto
significa que cada llamada a aleatorio() puede generar un nuevo elemento de la serie
basado en el último número sin la declaración global de dicha variable.
Un detalle importante es que la variable estática azar nunca se inicializa
explícitamente. Esto significa que la primera vez que se llama a la función aleatorio()
tendrá el valor cero por defecto. Aunque esto es aceptable en algunas aplicaciones, la
mayoría de los generadores de series necesitan un punto de partida flexible.
Para conseguir esto es necesario que se inicialice azar antes de la primera
llamada a aleatorio(), lo que podría realizarse fácilmente si azar fuera una variable
global. Sin embargo, el evitar tener que hacer global a azar fue el motivo de comenzar la
declaración con static. Esto conduce al segundo uso de static : como variable global.
#include <stdio.h>
#include <conio.h>
#define LIMITE 10
unsigned aleatorio(void);
void inicia_semilla(void);
void main(void)
{
unsigned cont;
inicia_semilla();
for (cont=1;cont<=LIMITE; cont++)
printf("%u\n",aleatorio());
__________________________________________________________________________________________________________
Comenzando a programar 18
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
getch();
}
unsigned aleatorio(void)
{
azar=(azar*25173+13849) % 65536;
return (azar);
}
#include <stdio.h>
#define LIMITE 10
unsigned aleatorio(void);
void iniciasem(unsigned);
void main(void)
{
unsigned int cont,semilla;
printf("La semilla debe ser numero entre 1 y 65535(0=FIN).\n ");
do
{
printf("\n\nSemilla= ");
scanf("%u",&semilla);
if (semilla)
{
iniciasem(semilla);
for (cont=1;cont<=LIMITE; cont++)
printf("%6u", aleatorio());
}
}
while (semilla);
}
//************************************************************
static unsigned azar;
void iniciasem(unsigned x)
{
azar=x; //Se inicializa una sola vez
}
//************************************************************
__________________________________________________________________________________________________________
Comenzando a programar 19
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
unsigned aleatorio(void)
{
azar=(azar*25173+13849) % 65536;
return (azar);
}
• register.
8. EL PREPROCESADOR.
__________________________________________________________________________________________________________
Comenzando a programar 20
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
• #define
#define TRUE 1
#define FALSE 0
Una vez que se define un nombre de macro, se puede utilizar como parte de la
definición de otros nombres de macro. Por ejemplo, el siguiente código define los
nombres UNO, DOS y TRES como sus valores respectivos:
#define UNO 1
#define DOS UNO+UNO
#define TRES UNO+DOS
printf (MSJE_ERR);
__________________________________________________________________________________________________________
Comenzando a programar 21
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
• #include
__________________________________________________________________________________________________________
Comenzando a programar 22
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
9. COMPILACIÓN Y ENLAZADO.
• Biblioteca y enlace.
• Compilación separada.
__________________________________________________________________________________________________________
Comenzando a programar 23
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
• Normalmente, los programas hacen llamadas a diversas rutinas o módulos, bien del
propio usuario, o bien del sistema (funciones de la biblioteca), que deben unirse al
programa principal. Las diferentes rutinas del usuario pueden estar repartidas en
varios archivos. El enlazador será el encargado de combinar físicamente todos esos
archivos para generar el archivo ejecutable. Antes de unirse o montarse los
diferentes archivos deben estar compilados.
• Todos los archivos objeto a enlazar han de ser reubicables, en el sentido de que el
direccionamiento que utilizan es relativo, o sea, consideran que su primera
instrucción comienza en la dirección 0 de memoria. Además, cada vez que desde un
archivo se accede a código de otro archivo (cuando se produce una llamada a una
función o se utiliza una variable global situadas en otro archivo) el compilador crea
una referencia externa. Pues bien, es el enlazador el encargado de sustituir las
referencias externas por las direcciones relativas apropiadas.
__________________________________________________________________________________________________________
Comenzando a programar 24
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
Aunque las bibliotecas son parecidas a los archivos objeto, tienen una diferencia
crucial: no todo el código de la biblioteca se añade al programa. Cuando se enlaza un
programa que consta de varios archivos objeto, todo el código de cada archivo objeto
forma parte del programa ejecutable final. Esto sucede tanto si se utiliza como si no se
utiliza el código. En otras palabras, todos los archivos objeto especificados en tiempo de
enlace se «añaden conjuntamente» para formar el programa. Sin embargo, no es éste el
caso de los archivos de biblioteca.
Una biblioteca es una colección de funciones. A diferencia de un archivo objeto,
un archivo de biblioteca almacena el nombre de cada función, el código objeto de la
función y la información de reubicación necesaria para el proceso de enlace. Cuando un
programa hace referencia a una función contenida en una biblioteca, el enlazador busca
esa función y añade el código al programa. De esta forma, sólo se añaden al archivo
ejecutable las funciones que se utilizan en el programa.
Puesto que las funciones que son facilitadas con los compiladores de C se
encuentran en una biblioteca, sólo se incluirán en el código ejecutable del programa las
que éste utilice (si se encontraran en archivos objeto, todos los programas que se
escribieran tendrían un tamaño de varios cientos de miles de bytes).
En una biblioteca estática el enlazador extrae los módulos precisos cuando enlaza,
y los inserta en el fichero que contendrá la imagen ejecutable. En cambio en una
biblioteca dinámica el enlazador solamente guarda en el fichero ejecutable unas
referencias a las posiciones donde están las funciones requeridas en la biblioteca. Al
ejecutar el programa en realidad se ejecuta un programa llamado cargador en tiempo
de ejecución (run-time loader) que carga en memoria las funciones necesarias de la
biblioteca dinámica en ese momento y ejecuta así el programa completo.
Al usar bibliotecas estáticas los ficheros ejecutables son más grandes, pues
contienen el código de las funciones de biblioteca. El gasto de memoria del sistema es
mayor, pues para cada programa distinto que se esté ejecutando en cierto momento y
que use cierta función, provocará que dicha función esté repetida en memoria.
Al usar bibliotecas dinámicas los ficheros ejecutables son más pequeños, pues sólo
contienen referencias a las funciones, pero se tarda más en cargar el programa en
memoria: al iniciar la ejecución, el cargador en tiempo de ejecución debe buscar las
funciones y cargarlas. Si además la biblioteca es compartida y si cualquier función ya
está en memoria porque algún otro programa la esté usando, se utilizará esa misma
copia, por lo cual el gasto de memoria es menor. Un inconveniente de las bibliotecas
dinámicas es que los archivos deben estar siempre disponibles.
EXTENSIONES DE BORLAND.
Como se sabe, C es un lenguaje bastante pequeño, ya que sólo tiene treinta y dos
palabras reservadas; por sí mismo no tiene capacidades de entrada/salida, ni de manejo
de memoria dinámica, etc. Todo eso se lleva a cabo mediante funciones externas. Poco
transportable sería un programa, y de nada serviría tener un lenguaje normalizado, si
para hacer cualquier cosa hubiera que usar una función proporcionada por un fabricante,
y cada uno de ellos tuviera una distinta para hacer lo mismo. Por tanto se hizo evidente
para el comité ANSI, encargado de la normalización del lenguaje, que también había
que encargarse de un conjunto de funciones mínimo pero lo más general y extenso
posible.
Este conjunto de funciones se ha basado en su mayor parte en unas que han
existido prácticamente desde que Dennis Ritchie construyó el primer compilador, o que
se han ido añadiendo en máquinas UNIX a lo largo del tiempo. No obstante, y por
supuesto, todas funcionan en cualquier compilador ANSI y en cualquier sistema
operativo; si no, no se hablaría de estándar, evidentemente.
Así que en 1989 el comité X3JI1 del organismo ANSI (American National
Standards Institute) terminó de escribir el documento de normalización para el lenguaje
y las funciones; poco antes, otro Organismo internacional, ISO (International Standards
Organization), formó otro Comité para lo mismo, pues veían que el de ANSI era
demasiado americano. Éstos, para evitar que hubiera dos estándares distintos, se
pusieron de acuerdo y aceptaron incluir en la biblioteca unas cuantas funciones nuevas y
modificar otras, de forma que se pudiera escribir un programa que tuviera en cuenta las
características culturales de cada país, como los separadores de decimales en números, o
letras formadas por más de un carácter. Aunque ISO acabó su trabajo en 1990, éste sólo
difiere del de ANSI en la redacción.
El comité de ISO ha seguido trabajando y, a propuesta de varios grupos y
subcomités, en septiembre de 1994, publicó un artículo suplementario. A partir de 1995
nuevas reuniones quizá produzcan la versión 2 del estándar ISO de C, y quizá esto
influya en modificaciones a la biblioteca estándar.
Por supuesto, las funciones normalizadas que conforman la biblioteca estándar
estarán presentes en un ambiente de cómputo normal, un sistema con teclado y monitor;
los programadores de sistemas como robots o máquinas, así como los escritores de la
propia biblioteca pueden no tener ésta presente y usar solamente C puro. Por otra parte
existen evidentemente muchísimas más bibliotecas especializadas en cualquier tema:
gráficos, sistemas de ventanas, red, seguridad, bases de datos, procesos, matemáticas
especializadas, etcétera. Pero si un programa se puede escribir usando solamente la
biblioteca estándar, podemos estar (casi) seguros de que se podrá transportar a cualquier
ordenador que tenga un compilador C ANSI/ISO.
Borland C++ cumple con el estándar ANSI y suministra todas las funciones
definidas por éste. Sin embargo, para permitir una utilización y un control de la
computadora lo más completos posible, Borland C++ contiene muchas funciones
adicionales no definidas por el estándar ANSI. Dichas extensiones incluyen un conjunto
completo de funciones de pantalla y gráficos para DOS, funciones especiales de
asignación de 16 bits y funciones de directorio. Tal como acabamos de decir, siempre
que no se piense transportar los programas que se escriban a un entorno diferente, se
pueden utilizar perfectamente estas funciones extendidas.
Las funciones de la biblioteca usan ampliamente una serie de tipos y macros del
preprocesador, que se definen apropiadamente en el fichero de cabecera adecuado. En
estos ficheros también se declaran los prototipos de las funciones correspondientes, y
quizá alguna variable. Existen 15 cabeceras normalizadas; cada una de ellas
corresponde a un grupo de funciones con un determinado campo de acción; por
ejemplo, en time.h se declaran tipos, macros y funciones relativas a la información
sobre la fecha y hora.
No se debe confundir biblioteca con cabecera. Una biblioteca es un archivo que
contiene funciones y objetos compilados, en código máquina; podemos decir que ahí
están las definiciones de las funciones; el enlazador las extraerá y se cargarán en su
momento para su ejecución. En cambio, una cabecera suele ser un fichero de texto
con código fuente C; ahí están fundamentalmente las declaraciones de las funciones;
el preprocesador le pasa su contenido al compilador para su traducción. Existen quince
cabeceras normalizadas por ANSI, y tres más que fueron añadidas en septiembre de
1994 por ISO y tratan de la internacionalización en más detalle que el original
ANSI/ISO de 1990.
Cabeceras normalizadas
assert.h locale. h stddel. h
ctype. h math. h stdio. h
errno. h setjmp.h stdlib.h
float . h signal.h string. h
limits.h stdarg.h time.h
iso646.h wctype.h wchar.h
- Las cabeceras no tienen por qué ser ficheros de texto fuente; en algunos
sistemas pueden estar precompiladas, en un formato especial para que el
compilador tarde menos en hacer su trabajo. En otros ni siquiera tiene
que existir un fichero con ese nombre; puede que simplemente el
preprocesador ya sepa qué es lo que tiene que hacer al recibir la directiva
include. Sin embargo en UNIX las cabeceras sí son ficheros con texto,
código fuente en C; y normalmente están en el directorio /usr/include,
con lo que podemos curiosear tranquilamente.
- Todos los identificadores declarados en las cabeceras deben considerar se
como reservados; esto es, no deben redefinirse, ni usarse para otro
propósito distinto de aquél para el cual están pensados.
- Todos los nombres de macros o identificadores que empiecen por el
signo de subrayado (_) deben considerarse como de la categoría anterior.
Nunca utilice un identificador en su programa que empiece por dicho
signo.
- Las cabeceras pueden incluirse en cualquier orden, incluso más de una
vez, pero antes de que se use cualquier función o marro definida en ella.
Suelen ponerse al principio del fichero. Use siempre la notación de
ángulos para las cabeceras estándar; por ejemplo,
__________________________________________________________________________________________________________
Comenzando a programar 27
I.E.S. Francisco Romero Vargas –Departamento de Informática - Fundamentos de Programación
__________________________________________________________________________________________________________
Es decir, si por ejemplo consideramos un tipo float, que ocupa 4 bytes, cada vez que se le sume
una cantidad k a un puntero a float , el número de posiciones o bytes que avanza el puntero es de 4 x k.
Así pues, cuando un puntero se incrementa (decrementa) avanza (retrocede) tantos bytes como
los que ocupa su tipo base; y si se le suma (resta) un entero k, avanzará (retrocederá) k por número de
bytes que ocupa su tipo base.
Además de la suma y resta entre punteros y enteros, la única operación aritmética permitida es la
resta entre punteros. Este tipo de operación sólo tiene sentido cuando ambos punteros apunten a un
objeto común, como por ejemplo, un array. Este tipo de resta obtiene el número de elementos del tipo
base que separan el valor de los dos punteros.
Salvo estos tipos de operaciones, el resto de las operaciones aritméticas no están permitidas: no
se pueden multiplicar, dividir ni sumar punteros; tampoco se puede sumar o restar tipos float o
double a punteros.
__________________________________________________________________________________________________________
Comenzando a programar 28