Apuntes de C

Libros:
    "The C programming language". B.W. Kernighan, D.M. Ritchie.
    "A book on C". A. Kelley, I. Pohl.
    "Introductory C". R.L. Petersen.

Páginas:
      The C Book, second edition. M. Banahan, D. Brady, M. Doran       http://publications.gbdirect.co.uk/c_book/
     Dave Marshall     http://www.cs.cf.ac.uk/Dave/C/
    Links de Henrik Herranen   http://www.lysator.liu.se/c/

  1. Instrucciones
  2. Funciones
  3. Punteros y matrices 
  4. Constantes, estructuras y variables
  5. Librerías
  6. Compilador

1. Instrucciones


break
Permite salir de un ciclo for, while, do-while o de switch (ver este último).


case
Es parte de switch .


continue
Sirve para pasar a la siguiente iteración en un ciclo for, while o do-while . Se emplea poco. Por ejemplo, si a[] es una lista de números, suponiendo incluido <math.h> , el siguiente subprograma halla las raíces cuadradas de los positivos y deja los negativos igual.

for (i=0;i<=LONGITUD;++i){
if (a[i]<0)
    continue;
a[i]=sqrt(a[i]);
}


Evidentemente se podría hacer algo igual con if-else .

default
Es parte de switch .


do
Indica el comienzo de un bloque while en el que la condición se comprueba al final. Esto es, do instrucciones while(condición) ; ejecutará las instrucciones mientras se satisfaga la condición.

else
Se usa en conjunción con if .


for
Un bucle del tipo

for (expr1;expr2;expr3)
instrucciones


Es equivalente a

expr1;
while (expr2){
    instrucciones
    expr3;
}


Hay que tener cuidado con el uso de las llaves y los punto y coma. Considerando los programas:

main(){

int i;
int j=333;

for (i=0;i<=4;i++);
printf("%d\t",i);
printf("%d\t",j);
}

main(){

int i;
int j=333;

for (i=0;i<=4;i++)
printf("%d\t",i);
printf("%d\t",j);
}

main(){

int i;
int j=333;

for (i=0;i<=4;i++){
printf("%d\n",i);
printf("%d\n",j);
}

}

Las salidas serán:

5       333
0       1       2       3       4       333
0       333     1       333     2       333     3       333     4       333

En el primer caso, el bucle está vacío.

getchar
Espera un carácter (introducido a través del teclado) y da su código. Requiere <stdio.h> .
Por ejemplo (K & R  p.17) :
 
main(){

int c;

c=getchar();

while (c!=EOF){
    putchar(c);
    c=getchar();
}
}


Repite todo lo que se introduce por el teclado. El efecto de Ctrl-D es 'fin de datos'. De este modo al escribir abc(+enter)  aparece abc en la siguiente línea, pero abc(+Ctrl-D) repite abc a continuación. Dando dos veces a Ctrl-D se sale del programa .

Aparentemente el programa no puede escribir en pantalla hasta que no se ha desocupado de la introducción de datos.

El programa anterior se puede escribir más brevemente (K & R  p.18) como

main(){

int c;

while ((c=getchar())!=EOF){
    putchar(c);
}
}


Ejecutar a.out < fichero.txt es lo mismo que cat fichero.txt .

goto
Permite saltar a otra parte del programa. Se desaconseja su uso  excepto en el caso en que haya que salir de muchos bucles anidados (actuando como equivalente de un " break múltiple"). Su estructura es goto etiqueta ; donde etiqueta: debe incluirse en el  punto al que se quiere saltar. Ejemplo:

main(){
printf("Este programa comienza aquí.\n");
goto salto;
printf("No pasa por esta línea.\n");
salto:
printf("Y salta hasta aquí.\n");
}


El resultado es:

Este programa comienza aquí.
Y salta hasta aquí.



if
Sirve para tomar decisiones, su estructura general es

if (condición)
    instrucciones
else if (condición)
    instrucciones
else
    instrucciones

El bloque else if se puede repetir cuantas veces sea necesario. Las condiciones involucran típicamente operadores lógicos o relaciones recogidos en la tabla siguiente:

Operador
Significado
>
mayor
>=
mayor o igual
<
menor
<=
menor o igual
Operador
Significado
==
igual
!=
distinto
&&
y
||
o

No hay que confundir == con =, el segundo se utiliza para asignaciones. De la misma forma, & y | son los operadores AND y OR bit a bit y no deben confundirse con && y || que están  reservados para condiciones. Cuando se emplea una expresión en lugar de la condición, todo funciona como si la condición fuera que la expresión sea no nula.
    Se puede emplear  (condición)? expr1 : expr2 para indicar expr1 si se cumple la condición y expr2 si no se cumple. De modo que los siguientes programas para hallar el máximo de dos números son equivalentes:

main(){
float a,b,c;
printf("Introduzca dos números\n");
scanf("%f %f", &a,&b);
if (a>b)
    c=a;
else
    c=b;

printf("Su máximo es %f.\n",c);
}

main(){
float a,b,c;
printf("Introduzca dos números\n");
scanf("%f %f", &a,&b);

c=(a>b)?a:b;

printf("Su máximo es %f.\n",c);
}



printf
Sirve para imprimir en pantalla. En principio requiere <stdio.h> pero con gcc y cc  no hace falta incluir esta librería.

Ejemplo:

printf("%2c %5c %10f %4.1e %3.2f %12g" " %s\n",'A','B',70.1/7.2,70.1/7.2,70.1/7.2,70.1/7.2,"hola");

da lugar a:
 A     B   9.736111 9.7e+00 9.74      9.73611 hola

Los números indican el número de caracteres que debe ocupar cada cosa a imprimir y el número tras del punto, el número de decimales (ambos parámetros son opcionales). Por defecto se completa el número de caracteres poniendo espacios a la derecha, pero con un signo menos se puede cambiar. Por ejemplo, %-7.2f indica un número con dos decimales que ocupa siete espacios y que se justifica a la izquierda. Las claves para los tipos de variables que se quieren imprmir son:

Clave
Significado
c
carácter
d
entero decimal
e
notación científica
f
coma flotante
u
entero sin signo
Clave
Significado
g
científica si |exp|>3
x
entero hexadecimal (en mayusc. si X)
s
cadena de caracteres
p
puntero
o
número octal


También se puede considerar printf como una función que devuelve el número de caracteres impresos. Por ejemplo:

int a;
a=printf("%s\n","hola");
printf("%d\n",a);

da como resultado:  hola 5.

putchar
Imprime un carácter . Requiere <stdio.h> .  Es el "inverso" de getchar.  Véase el ejemplo allí mostrado.

return
Devuelve el resultado de una función.  No necesariamente todas la funciones  terminan con esta instrucción, porque pueden ser de tipo void . Es posible que su argumento sea una expresión más complicada que una simple variable. Por ejemplo, el siguiente programa imprime una tabla de valores de la función  x^3-x+1 en [0,1].

float funcion(float);

main(){
float x;
for (x=0; x<1.01; x+=0.1)
printf("x= %.2f    f(x)= %.2f\n",x,funcion(x));
}

float funcion(float t){
return (t*t*t-t+1);
}


scanf
Sirve para inroducir datos (a través del teclado). Requiere <stdio.h> .

Es igual que printf salvo que los datos se deben cargar en direcciones (punteros) en vez de en las variables mismas. También puede entenderse como una función que da el número de datos correctamente introducidos.
Por ejemplo, al ejecutar:

int a,v;
float b;
double c;
v=scanf("%d %f %lf",&a,&b,&c);
printf("\n Valores tecleados:\n%9d %6f %9lf",a,b,c);
printf("\n %d\n",v);

e introducir:    12 (enter), 12.1 (enter),  12.2 (enter) se obtiene 

Valores tecleados:
       12 12.100000 12.200000
 3


Si el primero no fuera entero se hace un lío, actuando como si la  parte decimal fuera el segundo número. Aquí  "lf" se refiere a un número en coma flotante "double" y "Lf" es uno "long double". La cadena entre comillas en el primer argumento de scanf indica el formato de los datos. Por ejemplo, la instrucción scanf("uno=%d, dos=%d",&a,&b); requiere teclear algo de la forma  uno=1234, dos=5678 .

sizeof
Indica el número de bytes que ocupa un tipo de variable, expresión, matriz o estructura. Por ejemplo

int incr(int);

main(){
int a[4]={2,7,5,6};
int b=2,c=3;

printf("Los caracteres ocupan %d bytes\n",sizeof(char));
printf("Los enteros ocupan %d bytes\n",sizeof(int));
printf("Los reales simples ocupan %d bytes\n",sizeof(float));
printf("Los \"long double\" ocupan %d bytes\n",sizeof(long double));
printf("La matriz entera a[4]={2,7,5,6} ocupa %d bytes\n",sizeof(a));
printf("b, c y b+c ocupan %d, %d y %d bytes\n", sizeof(b),sizeof(c),sizeof(a+b));
printf("La función incr ocupa %d bytes\n",sizeof(incr));
}

int incr(int n){
n++;
return n;
}

imprime:

Los caracteres ocupan 1 bytes
Los enteros ocupan 4 bytes
Los reales simples ocupan 4 bytes
Los "long double" ocupan 12 bytes
La matriz entera a[4]={2,7,5,6} ocupa 16 bytes
b, c y b+c ocupan 4, 4 y 4 bytes
La función incr ocupa 1 bytes



switch
Es una generalización de if . La estructura general es

switch (expresión){
   
case valor1:
        instrucciones
   
case valor2:
        instrucciones
    ........    ..........
   
default:
        instrucciones
}


Si la expresión toma el valor1 se  salta al primer bloque, si  toma el valor2 al segundo, etc. Si no toma ninguno de ellos se pasa a default. Normalmente cada bloque case acaba con  break lo cual permite salir de  switch (de otro modo se pasaría por el código del siguiente caso). El break en default es opcional.
    Ejemplos:

main(){
char c;
printf("Introduce una vocal en minúsculas\n");
c=getchar();

switch (c){
    case 'a':
    printf("Es la primera\n");
    break;
    case 'e':
    printf("Es la segunda\n");
    break;
    case 'i':
    printf("Es la tercera\n");
    break;
    case 'o':
    printf("Es la cuarta\n");
    break;
    case 'u':
    printf("Es la quinta\n");
    break;
    default:
    printf("No es una vocal en minúsculas\n");
    break;
}
}


main(){
char c;
printf("Introduce una vocal en minúsculas\n");
c=getchar();

switch (c){
    case 'a':
    case 'e':
    case 'o':
    printf("Es fuerte\n");
    break;
    case 'i':
    case 'u':
    printf("Es débil\n");
    break;
    default:
    printf("No es una vocal en minúsculas\n");
    break;
}
}



while
Tiene la estructura:

while (condición){
        instrucciones
}

Las instrucciones se ejecutarán mientras se satisfaga la condición. El siguiente programa de ejemplo es un contador de palabras. En la máquina en curso, una vez compilado, a.out<fichero da el número de palabras del fichero.


main(){
char flag=0; /* 1 dentro de la palabra, 0 fuera */
char c;
int n=0;

while((c=getchar())!=EOF){
// Comprueba si es un espacio, cambio de línea o tabulación
    if(c==' ' || c== '\n' || c=='\t')
        flag=0;
    else if (flag==0){
        flag=1;
        ++n;
        }
}
printf("El número de palabras es %d.\n",n);
}



    Otro programa parecido es el siguiente que escribe cada palabra en una línea (solución del ejercicio 1.12 de K & R).


main(){
char flag1,flag2; /*espacio o no espacio en carácter previo y actual */
char c1=111,c2; /*carácter previo y actual */
int n=0;

while((c2=getchar())!=EOF){
//Comprueba si c1 y c2 son un espacio, cambio de línea o tabulación
flag1=(c1==' ' || c1== '\n' || c1=='\t');
flag2=(c2==' ' || c2== '\n' || c2=='\t');

// Comprueba si comienza palabra, y en ese caso cambia de línea
if((!flag1)&&flag2)
    printf("\n");
//Imprime la letra
if (!flag2)
    printf("%c",c2);
c1=c2;
}
}



El argumento de while puede ser una expresión en lugar de una condición. Se ejecutarán las instrucciones mientras la expresión no valga cero.

2. Funciones

Antes de usar una función hay que declararla creando lo que se llama un prototipo de función con algo de la forma:
tipo_de_resultado  nombre_de_función(tipo_de_argumento_1, ... ,tipo_de_argumento_n);
El tipo de argumento puede ir acompañado de un nombre arbitrario de variable, aunque no es necesario. La propia definición de la función comienza de esta manera y los argumentos, si los tiene, son imprescindibles y su código va encerrado entre llaves. La instrucción para devolver un resultado es return .
    El primer ejemplo contiene una función con un argumento y un resultado y el segundo una con argumento y sin resultado.

int doble(int);  /* Prototipo de función */

main(){
int i;
for(i=1; i<5; ++i)
printf("El doble de %d es %d\n", i, doble(i));
}

int doble(int j){
j*=2;
return j;
}
void asteris(int);  /* Prototipo de función */

main(){
int i;
for(i=1; i<5; ++i)
asteris(i);
}

void asteris(int j){
for ( ;j>=1; --j)
putchar('*');
putchar('\n');
}

Sus salidas son:

El doble de 1 es 2
El doble de 2 es 4
El doble de 3 es 6
El doble de 4 es 8
*
**
***
****


Los argumentos de las funciones se pasan "por valor", es decir, el nombre de la variable es indiferente. Si se quieren hacer llamadas "por referencia" hay que usar punteros.
Ejemplo:


void no_cambio(int);
main(){

int a=1;
printf("El valor inicial es: %d\n", a);
no_cambio(a);
printf("Y el final es: %d\n", a);
}

void no_cambio(a)
{
a=2;
}


da como resultado:

El valor inicial es: 1
Y el final es: 1

Sin embargo:

void si_cambio(int*);

main(){
int a=1;

printf("El valor inicial es: %d\n", a);
si_cambio(&a);
printf("Y el final es: %d\n", a);
}

void si_cambio(int *puntero)
{
*puntero=2;
}

void si_cambio(int a[]);

main(){
int a[1];
a[0]=1;

printf("El valor inicial es: %d\n", a[0]);
si_cambio(a);
printf("Y el final es: %d\n", a[0]);
}

void si_cambio(int s[]){
s[0]=2;
}


dan  como resultado:

El valor inicial es: 1
Y el final es: 2

Las funciones en C admiten recursión, por ejemplo, el siguiente programa calcula el factorial:

int fact(int);

main(){
int i;
printf("Introduce un entero no negativo pequeño: ");
scanf("%d",&i);
printf("Su factorial es %d\n",fact(i));
}

int fact(int n){
if (n<=1)
    return 1;
else
    return (n*fact(n-1));
}

    Si no se crea un prototipo de función hay que poner su definición antes de su llamada (¿?).

void
En el  argumento o el resultado de un prototipo de función indica que dicha función no necesita argumento o que no devuelve resultado.


    La función main comparte las propiedades del resto de las funciones. en particular puede tener argumentos y resultado. En K & R se suele acabar main con return 0 ; y en la página 26 se dice: "Típicamente un valor de retorno nulo implica una finalización normal, mientras que los no nulos señalan una finalización inusual o errónea". En K & P no se da ningún valor de retorno a main , es siempre de tipo void .
    Los argumentos de main , llamados convencionalmente argc y arg[] son un entero y una matriz de punteros. El primero indica el número de comandos que se han utilizado para lanzar el programa, y cada entrada de arg[] apunta a cada uno de los comandos. Por ejemplo, si creamos el programa ejem.c cuyo código es:

main(int argc,char *arg[]){
printf("Número de comandos: %d\n",argc);
printf("Primer comando: %s\t",arg[0]);
printf("Segundo comando: %s\t",arg[1]);
printf("Tercer comando: %s\n",arg[2]);
}

y lo compilamos para obtener a.out que ejecutamos con a.out tres palabras, el resultado será:

Número de comandos: 3
Primer comando: a.out   Segundo comando: tres   Tercer comando: palabras




 3. Punteros y matrices

Matrices
    La manera de declarar matrices es
tipo nombre[tamaño];

Sus elementos son nombre[0], nombre[1], ... , nombre[tamaño-1] .  Una matriz se puede iniciar escribiendo algo de la forma
nombre[tamaño]={valor1,valor2,...};
El tamaño es opcional. Las matrices n-dimensionales son simplemente  matrices de matrices, no tienen una notación particular. Una cadena de caracteres es un tipo especial de matriz que se puede iniciar escribiendo su texto entre comillas en vez de poniendo entre llaves el valor de cada carácter.  Siempre tiene un caracter NULL (cero en ASCII) de fin de cadena, por tanto su longitud es la que vemos más uno.
Por ejemplo, el código de la izquierda da el resultado de la derecha:

main(){
int i,j;
int a[]={12,33,57};
char b[]="Buenos días";
int c[2][3]={{a[0],a[1],a[2]},{1,2,3}};
printf("\"%s\" mide %d aunque tiene %d  caracteres\n\n", b, sizeof(b), sizeof(b)-1);

for (i=0;i<3;i++)
printf("a[%d]=%d\t",i,a[i]);
printf("\n\n");

for (i=0;i<2;i++){
for (j=0;j<3;j++)
printf("c[%d,%d]=%d\t",i,j,c[i][j]);
printf("\n");
}
}



"Buenos días" mide 12 aunque tiene 11 caracteres

a[0]=12 a[1]=33 a[2]=57

c[0,0]=12       c[0,1]=33       c[0,2]=57
c[1,0]=1        c[1,1]=2        c[1,2]=3



Es posible concatenar  cadenas de caracteres dejando un espacio en blanco entre ellas (por ejemplo: "hola" "a todos" equivale a "hola a todos" ) lo cual sirve para escribir líneas muy largas sin problemas con el editor.

    Cuando al declarar y definir una matriz, el tamaño no concuerda con el número de elementos, el compilador completa con ceros. Por ejemplo:
int a[4]={1,2};        equivale a         int a[4]={1,2,0,0};
Un ejemplo más complicado (K & P p. 219) es   que la definición de la matriz tridimensional
int a[2][2][3]={{{1, 1, 0}, {2, 0, 0}}, {{3, 0, 0}, {4, 4, 0}}};
equivale a
int a[2][2][3]={{{1, 1}, {2}}, {{3}, {4, 4}}};


Punteros
 Un puntero es una variable que indica una dirección de memoria a partir de la cual típicamente está almacenado el valor de una variable. Con * precediendo al nombre del puntero, se indica el valor de la variable, y con & precediendo al nombre de la variable, la dirección del puntero. Los punteros hay que declararlos en términos del tipo de dato al que apuntan:
tipo de variable *puntero;
Con ello el compilador sabrá cuánto ocupan los datos que metemos en la dirección de memoria señalada por el puntero. Podemos operar indirectamente sobre cierta variable  usando  punteros. Por ejemplo, se tiene el siguiente programa y su salida:

main(){
int i=3;
int *puntero=&i;

printf("Valor de la variable i=%d\n",i);
(*puntero)++;
printf("Valor de la variable i=%d\n",i);
*puntero=(*puntero) * 7;
printf("Valor de la variable i=%d\n",i);
}

Valor de la variable i=3
Valor de la variable i=4
Valor de la variable i=28

Esta forma de actuar, permite que las funciones puedan modificar sus propios argumentos (ver más arriba el ejemplo de la función si_cambio ).
    Al incrementar un puntero, éste pasa a apuntar al siguiente dato (no siempre se incrementa uno, depende de lo que ocupe el dato). Esto se ve claramente al tratar con matrices. Por ejemplo:

main(){
int a[3]={2,7,5};

int *puntero;

puntero=&a[0];

printf("\n\nDirección hexadecimal %x\n",puntero);
printf("Valor de la variable %d\n",*puntero);
printf("Incrementa el puntero\n");
puntero++;
printf("\n\nDirección hexadecimal %x\n",puntero);
printf("Valor de la variable %d\n",*puntero);
printf("Incrementa el puntero\n");
puntero++;
printf("\n\nDirección hexadecimal %x\n",puntero);
printf("Valor de la variable %d\n",*puntero);
}


El resultado de ejecutar el programa anterior en la máquina en uso está escrito a continuación a la izquierda. Como cada entero ocupa cuatro bytes, el puntero en cada incremento añadirá cuatro a la dirección de memoria a la que apunta. A la derecha está lo que se obtendría al cambiar el tipo  de variable de a[] y *puntero a char (y reemplazando consecuentemente %d por %c y la primera definición por a[3]={'2','7','5'};). En este caso cada carácter ocupa un byte y los incrementos se producen de uno en uno.


Dirección hexadecimal bffff520
Valor de la variable 2
Incrementa el puntero


Dirección hexadecimal bffff524
Valor de la variable 7
Incrementa el puntero


Dirección hexadecimal bffff528
Valor de la variable 5


Dirección hexadecimal bffff520
Valor de la variable 2
Incrementa el puntero


Dirección hexadecimal bffff521
Valor de la variable 7
Incrementa el puntero


Dirección hexadecimal bffff522
Valor de la variable 5


Matrices como punteros
 Una matriz es realmente un puntero, de hecho  la instrucción
puntero=&matriz[0];
que dirige el puntero  al comienzo de la matriz, es equivalente a
puntero=matriz;
Incrementar el puntero equivale a pasar al siguiente elemento de la matriz, por tanto
*(puntero+i)      es lo mismo que     matriz[i]
y esta duplicidad se refleja en la sintaxis hasta el punto que se puede emplear también puntero[i] o *(matriz+i) para representar este valor. Todo funciona como si el nombre de la matriz y del puntero fueran intercambiables. Incluso se puede definir un puntero a una cadena de caracteres con  char *puntero="cadena de caracteres"; y para imprimirla se puede escribir printf("%s",puntero); (lo que prueba que una cadena de caracteres es también un puntero). La prinicpal diferencia entre punteros y matrices es que  éstas  se comportan como punteros constantes que no admiten asignaciones ( matriz=otra_matriz y matriz++ son ilegales).
Como ejemplo de la equivalencia entre punteros y matrices (a la derecha está lo que imprime el programa):

main(){
int a[4]={2,7,5,6};
int *puntero=a;

printf("Primer valor %d\n",a[0]);
printf("Segundo valor %d\n",*(puntero+1));
printf("Tercer valor %d\n",puntero[2]);
printf("Cuarto valor %d\n",*(a+3));
}


Primer valor 2
Segundo valor 7
Tercer valor 5
Cuarto valor 6



Cuando una matriz participa  en una función, en realidad no se pasan sus valores sino el puntero que la representa. Así es lícito el programa:

int suma(int[]);

main(){
int matriz[3]={2,3,5};
printf("Suma de los tres elementos de la matriz: %d\n",suma(matriz));
}

int suma(int *puntero){
return *puntero+*(puntero+1)+*(puntero+2);
}


Las matrices de punteros o equivalentemente, los punteros apuntando a punteros, pueden servir como sustitutos de matrices con filas de longitud variable. Por ejemplo, si tuvieramos que almacenar muchas cadenas de caracteres de longitudes muy diversas podríanmos guardar sólo una lista (matriz) de punteros que indican dónde esta el principio de cada una de ellas. Según lo visto, la lista se puede representar con otro puntero, como muestra el siguiente código. La columna de la derecha tiene el mismo significado pero usando un puntero de punteros en vez de una matriz de punteros.

main(){

/* Esto es una matriz de punteros */
char *matriz_de_punteros[3];
/* Cada elemento de la matriz es un puntero y por tanto,según lo visto
antes, se puede dirigir a una cadena de caracteres igualándola a ella */
matriz_de_punteros[0]="Corta"; /* Primer puntero */
matriz_de_punteros[1]="Frase bastante más larga"; /* Segundo */
matriz_de_punteros[2]="Frase media"; /* Tercero */

printf("%s\n", matriz_de_punteros[0]);
printf("%s\n", matriz_de_punteros[1]);
printf("%s\n", matriz_de_punteros[2]);
}

main(){

/* Esto es una matriz formada por tres punteros */
char *matriz_de_punteros[3];

/* Crea un puntero de punteros que apunta a la matriz (al puntero del
primer lugar  */
char **ppuntero;
ppuntero=matriz_de_punteros;
/* También podría ponerse char **ppuntero=matriz_de_punteros; */

*ppuntero="Corta"; /* Puntero del primer lugar dirigido a "Corta" */
*(++ppuntero)="Frase bastante más larga";
*(++ppuntero)="Frase media";

ppuntero=matriz_de_punteros; /* Recupera la posición inicial */

printf("%s\n", *ppuntero);
printf("%s\n", *(++ppuntero)); /* Incrementa e imprime */
printf("%s\n", *(++ppuntero));

}


Si uno quisiera ordenar una lista de palabras por orden alfabético, todo lo que habría que hacer es ordenar adecuadamente los punteros que apuntan a ellas, no hay que mover de ninguna manera las cadenas de caracteres correspondientes, con el correspondiente ahorro de tiempo y memoria.


Punteros y funciones

 Un puntero  no sólo puede apuntar a variables, sino también a funciones. La declaración de un puntero de este tipo es de la forma:
tipo de resultado de la función  (*puntero)(argumentos de la función);
Los paréntesis alrededor del puntero son importantes. Omitirlos definiría una función que toma punteros como valores.  Si puntero apunta a una función, entonces (*puntero) es el valor de dicha función. Es decir, es posible evaluar funciones a través de punteros. El siguiente programa calcula 2*(a+b) :

int doble_de_suma(int ,int );

main(){

int a=5,b=3;
int (*puntero)(int,int);
puntero=doble_de_suma;
printf("Doble de a+b = %d\n",(*puntero)(a,b));
}

int doble_de_suma(int n,int m){
n+=m;
n*=2;
return n;
}

Por otra parte, al igual que ocurría con las matrices, las funciones en sí mismas pueden considerarse punteros. Esto es interesante para poder hacer que ciertas funciones sean argumentos de otras. Por ejemplo, el siguiente programa  calcula el valor de la parte real de una suma trigonométrica entre a y b con la fase f. La función suma_trigonométrica admite estos tres argumentos permitiendo elegir la fase siempre que la hayamos definido antes.

#include <math.h>

float suma_trigonometrica(int ,int, float (*)(int ) );
float f(int);
float g(int);

main(){
int a=1,b=13; /* valores arbitrarios. Límite inferior y superior */

printf("Suma de Gauss de %d a %d: %f\n", a,b,suma_trigonometrica(a,b,f));
printf("Núcleo de Dirichlet en .1 de %d a %d: %f\n", a,b,suma_trigonometrica(a,b,g));
}

float suma_trigonometrica(int lim_inf,int lim_sup, float (*puntero)(int ) ){
float suma=0;
int i;
for (i=lim_inf; i<=lim_sup; i++)
suma+=cos(2*M_PI*(*puntero)(i)); /* M_PI está definido como pi en math.h */
return suma;
}

float f(int n){
float x=n;
return (x*x/13);
}

float g(int n){
float x=n;
return (.1*x);
}


La declaración float suma_trigonometrica(int ,int, float (*)(int ) ); indica que los dos primeros argumentos de la función son enteros y el tercero es un puntero  que apunta a una función que toma valores enteros y los aplica en reales. Al llamar a  suma_trigonometrica debemos escribir como tercer argumento el nombre de la función que da la fase deseada pues ella misma es ya un puntero. De nuevo la sintaxis es bastante maleable y permite escribir puntero(i)  en vez de (*puntero)(i) . Esto es análogo al caso de las matrices.


    Instrucciones como printf tienen un número variable de argumentos. Es posible crear funciones de este tipo usando la librería <stdarg.h> .  En ella va_list , va_start, va_end y va_arg son una especie de análogos de FILE , fopen, fclose y getc para los argumentos de la función.



 4. Constantes, variables y estructuras


Tipo
Rango
Bytes
char
-128, 127
1
short int
-32768, 32767
2
int
-2147483648=-2^31,  2147483647=2^31-1
4
float
-3.4028235E38=-2^128,  3.4028235E38=2^128
4
double
-1.797693134E308=-2^1024,  1.797693134E308=2^1024
8
long double
 ¿no  funciona? Imprime poniendo (double) antes de la variable.
12


Es mejor no emplear nombres de variables que comiencen por un guión bajo, porque así se hace en las variables de las librerías.

Las constantes se suelen escribir en mayúscula. Por ejemplo, M_E, M_PI, M_SQRT2 y HUGE_VAL son los nombres que la librería <math.h>  da a e, pi, raíz de 2 y más infinito (?!).

Las operaciones básicas que se pueden hacer con variables numéricas para crear expresiones son +, -, *, / y %. La última indica módulo. Hay que tener cuidado con la división porque es tanto la entera como la de coma flotante, dependiendo del tipo de los argumentos. Por ejemplo, aunque a sea una variable float , escribiendo a=3/2; printf("Valor de a=%f\n",a); se obtendrá como resultado 1, lo correcto sería escribir  a=3.0/2.0. Además están las operaciones a bit &, |, ~, ^, << y >> que representan AND, OR, negación, XOR, shift a la izquierda y shift a la derecha.

    Escribir  ++ ó -- delante o detrás de una variable es un atajo para indicar que se incrementa o decrementa su valor. Si se escribe delante, el nuevo valor se empleará en la expresión en la que participe, y se se escribe detrás se usará el antiguo. Por ejemplo:

main(){
int a,b;
a=13;
b=a++;
printf("Valores de a y b=%d, %d\n",a,b);
a=47;
b=--a;
printf("Valores de a y b=%d, %d\n",a,b);
b=a--;
printf("Valores de a y b=%d, %d\n",a,b);
}

produce
Valores de a y b=14, 13
Valores de a y b=46, 46
Valores de a y b=45, 46


La sentencia variable+=expresión es un atajo para variable=variable+expresión. Lo mismo se aplica cambiando + por -, *, / ó %.

Las variables externas y estáticas (ver más abajo) se inician automáticamente en cero.

auto
Es el tipo de variable por defecto y por tanto rara vez se escribe. Por ejemplo, auto int a equivale a int a . Indica que la variable en cuestión sólo tiene sentido dentro de la función en la que está declarada y es invisible fuera de ella.

char
Declara una variable carácter, lo cual es un tipo especial de entero que sólo ocupa un byte . El valor  de un carácter es su código ASCII. Por ejemplo,
printf(" Como carácter: %c. Como número: %d.\n",'A'+1,'A'+1);
imprimiría: Como carácter: B. Como número: 66 .

Se admiten las siguientes  secuencias de escape representando caracteres especiales. Los de la primera tabla son de control:

Secuencia
Código
Significado
\n
10
Nueva línea
\t
9
Tabulación (hor.)
\v
11
Tabulación (ver.)
\b
8
Retroceso
\r
13
Retorno de carro
\f
12
Avance de página
\a
7
Campana

Secuencia
Código
Significado
\\
92
\
\?
63
?
\'
39
'
\"
34
"


Además \nml (con 0<=n,m,l<8) representa el carácter con código nml en octal  y \xnm el que tiene código hexadecimal nm (por tanto n y m son números de un dígito o letras de la 'a' a la 'f'). El programa con el que se obtuvieron los códigos de la tabla anterior es:

main(){
char escape[12]="\n\t\v\b\r\f\a\\\?\'\"";
int i;
for (i=0; i<=10; ++i)
printf("Carácter: %2d\t código: %d\n",i+1,escape[i]);
}


No parece haber diferencia entre \n y \r.

Ctrl-D es final de fichero y tiene código -1=255 en un byte. En la librería <stdio.h> la constante EOF  ( end of file ), está definida como constante -1. Ejecutando

main(){

int c;
while ((c=getchar())!=EOF){}
printf("%d",c);
}

y pulsando una vez Ctrl-D se obtiene -1.
Al pulsar Ctrl-C  o Ctrl-Z nos salimos del programa, éstos son caracteres de escape ¿tienen código?



const
Precediendo a la declaración de una variable indica que no se modificará, que es una constante. En realidad en la máquina en uso, la consecuencia de violar una declaración de este tipo se reduce a un aviso por parte del compilador. Así, al compilar el código:

main(){
const float PI=3.1416;
printf("El valor de PI es: %f\n", PI);
++PI;
printf("El valor de PI es: %f\n", PI);
}

Se recibe el aviso:
ejem.c: In function `main':
ejem.c:8: warning: increment of read-only variable `PI'


pero el ejecutable se construye igualmente y su efecto es:
El valor de PI es: 3.141600
El valor de PI es: 4.141600




double
Sirve para declarar una variable de doble precisión en coma flotante.


enum
Es una forma abreviada de hacer declaraciones de constantes. Su forma es
enum nombre_grupo {constante1,constante2, ...};
Con ello  constantej tomará el valor j-1. También se puede especificar el valor de la primera constante escribiendo constante1=k y los valores del resto de las constantes serán consecutivos, o especificar todos los valores El nombre_grupo es opcional. Esta instrucción es una alternativa a #define .  Por ejemplo, el siguiente código produce el resultado de la derecha

main(){

enum NUMEROS {CERO,UNO,DOS};
enum LETRAS {PRIMERA='a' , SEGUNDA , TERCERA };
enum MESES {ENERO='E', FEBRERO='F', MARZO='M'};
printf("Números: %d %d %d\n", CERO,UNO,DOS);
printf("Letras: %c %c %c\n", PRIMERA, SEGUNDA , TERCERA);
printf("Meses: %c %c %c\n", ENERO,FEBRERO,MARZO);
}


  Números: 0 1 2
Meses: E F M
 Letras: a b c


El nombre_grupo se puede usar para hacer declaraciones de variables. La instrucción:
enum nombre_grupo variable;
especifica que la variable solo puede tomar como valores las constantes especificadas en la enumeración del nombre_grupo . Estos dos usos de enum se pueden combinar en una sola línea escribiendo
enum nombre_grupo {constante1,constante2, ...} variable;

extern
Es en cierto modo, lo contrario de auto . Indica que una variable ya ha sido declarada fuera de una función en curso y sirve para que se pueda acceder a ella por referencia. Los códigos

int a=1;

void si_cambio(int);
main(){
extern int a;
printf("El valor inicial es: %d\n", a);
si_cambio(a);
printf("Y el final es: %d\n", a);
}

void si_cambio(a)
{
extern int a;
a=2;
}
int a=1;

void no_cambio(int);
main(){
int a=1;
printf("El valor inicial es: %d\n", a);
no_cambio(a);
printf("Y el final es: %d\n", a);
}

void no_cambio(a)
{
extern int a;
a=2;
}

producen:
El valor inicial es: 1
Y el final es: 2

El valor inicial es: 1
Y el final es: 1


En el segundo caso no se ha dicho que la variable a de  main sea la externa declarada al principio. Todo funciona como si en lugar de a escribimos otra letra. Es indiferente el cambio de la a externa en no_cambio porque no es la de main .
Según K & R p.174 , "Las variables externas nunca desaparecen. Como permanecen a lo largo de la ejecución del programa, se las puede usar para transmitir valores entre funciones. Sin embargo, pueden ser ocultadas si se redefine su identificador. Otro modo de entender las variables externas es considerarlas  como declaradas en un bloque que engloba al fichero completo".

float
Sirve para declarar una variable de precisión simple en coma flotante.

int

Indica variables enteras. Los rangos admisibles se pueden especificar con long y short.   Por ejemplo, en la máquina en uso long int son enteros con dos bytes (con signo) y por tanto el programa

short int i=32765;
short int j=i+3;

printf("%d   %d\n",i, j);


imprime 32765   -32768 .

Mientras que

main(){
long int i=2147483647;  /* Esto es 2^31-1 */

long int j=i+1;

printf("%d   %d\n",i, j);
}


imprime  2147483647   -2147483648 .

La ausencia de errores de  "rebosamiento" puede causar algunos resultados bastante raros. Por ejemplo, en la máquina en uso, al ejecutar:

main(){
int i=1;

for(i=1;;i*=2){
printf("%d\n",i);
}
}

se obtiene una lista indefinida de ceros.  Sin embargo cuando la tercera línea pasa a ser for(i=1;i<número_no_muy_grande;i*=2){ todo funciona como cabe esperar dando una lista de potencias de dos. La razón es que a partir de cierto  valor las potencias de dos son nulas con el formato int y no da tiempo a ver los primeros valores en pantalla.

Por defecto,int es long int.



long
Es el formato largo (con mayores rangos) de variables   int o double .

register
Antecediendo a la definición  de variables enteras, indica que son susceptibles de ser almacenadas en ciertos registros de alta velocidad de la memoria. No siempre el compilador puede cumplir este requisito. Una desventaja es que no se puede crear un puntero a estas variables.
    En la máquina en uso el primer programa tarda 56 segundos y el segundo 85.

main(){
register int i,j,k;
for (i=1;i<=100;i++){
for (j=1;j<=10000;j++){
for (k=1;k<=10000;k++)
;
}
printf("Iteración número %d\n",i);
}
}
main(){
int i,j,k;
for (i=1;i<=100;i++){
for (j=1;j<=10000;j++){
for (k=1;k<=10000;k++)
;
}
printf("Iteración número %d\n",i);
}
}



short
Es el formato corto (con menores rangos) de variables   int o double .

signed

Indica que la variable tiene signo, lo que puede modificar sus rangos. Por ejemplo, signed char c implica que -128 =< c < 128 y con unsigned char c se tendrá 0 =< c < 256. De este modo

main(){
signed char c1=129;
unsigned char c2=129;
printf("El valor de c1 es %d y el de c2 es %d\n", c1,c2);
}

imprime:  El valor de c1 es -127 y el de c2 es 129


static
Sirve para declar una variable estática. Ésta es como una variable externa (y por tanto no desaparece durante la ejecución del programa) que es invisible fuera del bloque en el que se declara como estática. No puede ser compartida por varios ficheros. Una utilidad de las variables estáticas, aparte de dar mayor modularidad a los programas con bloques que no interfieren aunque se repitan nombres, viene dada por el hecho de que las variables estáticas sólo se inician una vez, pudiéndose aprovechar el valor anterior en sucesivas llamadas a la función.

Ejemplo tomado de http://www-ee.eng.hawaii.edu/Courses/EE150/Book/chap14/subsection2.1.1.6.html
A  la derecha del código, arriba hay una posible ejecución, y debajo lo que ocurriría al quitar static

void suma_otro(void);

main(){
int count;
int sum=77;
printf("Introduzca cinco números enteros\n");

for (count=0;count<5;count++)
    suma_otro();
printf("El valor de sum fuera de suma_otro es %d\n",sum);
}

void suma_otro(void){
static int sum=0;

int num;

printf("Número?: ");
scanf("%d",&num);
sum+=num;
printf("Suma parcial= %d\n",sum);
}
Introduzca cinco números enteros
Número?: 1
Suma parcial= 1
Número?: 2
Suma parcial= 3
Número?: 2
Suma parcial= 5
Número?: 11
Suma parcial= 16
Número?: 23
Suma parcial= 39
El valor de sum fuera de suma_otro es 77


Introduzca cinco números enteros
Número?: 1
Suma parcial= 1
Número?: 2
Suma parcial= 2
Número?: 2
Suma parcial= 2
Número?: 11
Suma parcial= 11
Número?: 23
Suma parcial= 23
El valor de sum fuera de suma_otro es 77









struct
Crea  un nuevo objeto (estructura) compuesto por otros objetos o variables. Su forma general es
struct nombre_grupo {declaración de componentes} nombre(s)_estructura(s);
El nombre_grupo es opcional y su utilidad viene de que struct nombre_grupo nombre_estructura define una estructura que se ajusta al nombre_grupo anterior.
Por ejemplo:
struct complejo {float x; float y;} z;
declara que z es una estructura del tipo genérico llamado complejo que está formado por dos números float . Las estructuras se comportan como variables en el sentido de que pueden participar en asignaciones y funciones y se pueden crear punteros a ellas. Para acceder a las componentes de una estructura se escribe el nombre de la estructura y de la componente separados por un punto. Así en el ejemplo anterior z.x y z.y son la "parte real" y "parte imaginaria" de z .

Como ejemplo de asignación de las estructuras, los siguiente programas son equivalentes:

main(){
struct complejo {float x; float y;};
struct complejo z;

z.x=1.75;
z.y=0.33;

printf("Re(z)= %f, Im(z)=%f\n", z.x, z.y);
}
main(){
struct complejo {float x; float y;} z;

z.x=1.75;
z.y=0.33;

printf("Re(z)= %f, Im(z)=%f\n", z.x, z.y);
}
main(){
struct complejo {float x; float y;} z={1.75,.33};


printf("Re(z)= %f, Im(z)=%f\n", z.x, z.y);
}


    Las estructuras se pueden anidar, esto es, algunas de las componentes de una estructuras pueden ser a su vez estructuras. También pueden participar en matrices (o éstas ser parte de sus componentes). Sobre  todo cuando las estructuras son complicadas, es mejor referirse a ellas a través de punteros. Para indicar las componentes de la estructura a la que señala  un puntero hay  una notación especial que consiste en escribir el nombre del puntero, después -> y después el nombre la componente. Por ejemplo, si z es una estructura del tipo complejo, como antes, y p es un puntero que señala a z (esto es, p=&z ), entonces (*p).x equivale a p->x .

    Los dos siguientes programas de ejemplo calculan el área del paralelogramo determinado por los afijos de dos números complejos, usando en el primer programa una nueva estructura llamada paralel, y en el segundo caso una matriz de estructuras llamada del mismo modo. Ambos son sólo ilustrativos y podrían reducirse enormemente sin emplear punteros, estructuras ni matrices.

 
struct complejo {float x; float y;};
struct par {struct complejo lado1; struct complejo lado2;} paralel;
float area(struct par *);

main(){

printf("Introduce Re(lado1), Im(lado1), Re(lado2) e Im(lado1)\n");
scanf("%f %f %f %f", &(paralel.lado1.x), &(paralel.lado1.y), &(paralel.lado2.x),&(paralel.lado2.y));

struct par *p;
p=&paralel;

printf("El área del paralelogramo es %f\n", area(p));
}


float area(struct par *q){
float a;
a=(q->lado1.x)*(q->lado2.y)-(q->lado1.y)*(q->lado2.x);
if (a<0)
    a*=-1;
return a;
}
ruct complejo {float x; float y;} paralel[2];
float area(struct complejo *);

main(){

printf("Introduce Re(lado1), Im(lado1), Re(lado2) e Im(lado1)\n");
scanf("%f %f %f %f", &(paralel[0].x), &(paralel[0].y), &(paralel[1].x),&(paralel[1].y));

struct complejo *p;
p=&paralel[0];

printf("El área del paralelogramo es %f\n", area(p));
}


float area(struct complejo *q){
float a;
a=(q->x)*((q+1)->y)-(q->y)*((q+1)->x);
if (a<0)
    a*=-1;
return a;
}


    A veces algunas variables que conforman una estructura funcionan como flags y, para ahorrar memoria, se puede espeficiar el número de bits que ocuparán escribiendo tras ellos :número_de_bits. Por ejemplo, el siguiente prograna define una estructura que contiene el primer y último bits de un byte.


main(){
unsigned char c;
struct {unsigned char prim:1; unsigned char ult:1;} bit;
printf("Introduce un número de un byte (0-255)\n");
scanf("%d",&c );
bit.prim=((c&128)/128);
bit.ult=c&1;

printf("Primer y último bits = %d y %d.\n", bit.prim,bit.ult);
printf("La estructura ocupa %d byte\n",sizeof(bit));
}


Si la definición de la estructura fuera struct {unsigned char prim:1; unsigned char ult:1; float x;} bit;
 pasaría de ocupar un byte a ocupar ocho.

    Las estructuras también pueden aparecer como valores de retorno de funciones. El siguiente programa crea una función que multiplica un par de número complejos.

struct complejo {float x; float y;};

struct complejo producto(struct complejo,struct complejo);

main(){
struct complejo z,w;
printf("Parte real de z\n");
scanf("%f", &z.x);
printf("Parte imaginaria de z\n");
scanf("%f", &z.y);
printf("\nParte real de w\n");
scanf("%f", &w.x);
printf("Parte imaginaria de w\n");
scanf("%f", &w.y);
printf("\nRe(z·w)= %f, Im(z·w)=%f\n", producto(z,w).x, producto(z,w).y);
}


struct complejo producto(struct complejo multiplicador,struct complejo multiplicando){
struct complejo resultado;
resultado.x=multiplicador.x*multiplicando.x-multiplicador.y*multiplicando.y;
resultado.y=multiplicador.x*multiplicando.y+multiplicador.y*multiplicando.x;
return resultado;
}

En este programa  hay que repetir demasiadas veces struct complejo . La forma de evitarlo es con el comando  typedef .


typedef
Crea un nombre para un tipo de variable o estructura. Su sintaxis es
typedef antiguo_tipo nuevo_tipo ;
Por ejemplo, typedef int entero;
permite que podamos escribir entero en lugar de int . Se pueden concatenar varias de estas definiciones. Si nuevo_tipo es una matriz, antiguo_tipo se refiere a sus elementos. Por ejemplo, typedef float vectorR3[3]; crea el nuevo tipo vectorR3 y al escribir por ejemplo vectorR3 a; se declara que a es una matriz 1x3. De la misma forma typedef vectorR3 matriz[3]; declara matriz como una matriz de matrices 1x3, esto es, una matriz 3x3.
    De acuerdo con K & R p.146, typedef no crea un nuevo tipo realmente, sino que añade un nombre a los ya existentes y es el análogo de #define pero en el compilado.
    Se puede crear un nuevo tipo al tiempo que se crea una estructura. Por ejemplo, los siguientes programas son equivalentes y en cualquier caso crean un nuevo tipo de objetos imaginario, que son de la clases de la estructura complejo .

main(){
struct complejo { float x; float y;};
typedef struct complejo imaginario;


imaginario z={1.7,2.871};

printf("Re(z)= %f, Im(z)= %f\n", z.x, z.y);
}

main(){
typedef struct complejo { float x; float y;} imaginario;

imaginario z={1.7,2.871};

printf("Re(z)= %f, Im(z)= %f\n", z.x, z.y);
}


Usando typedef, el programa anterior para multiplicar números complejos sería:

typedef struct {float x; float y;} comple;

comple producto(comple,comple);

main(){
comple z,w;
printf("Parte real de z\n");
scanf("%f", &z.x);
printf("Parte imaginaria de z\n");
scanf("%f", &z.y);
printf("\nParte real de w\n");
scanf("%f", &w.x);
printf("Parte imaginaria de w\n");
scanf("%f", &w.y);
printf("\nRe(z·w)= %f, Im(z·w)=%f\n", producto(z,w).x, producto(z,w).y);
}


comple producto(comple multiplicador,comple multiplicando){
comple resultado;
resultado.x=multiplicador.x*multiplicando.x-multiplicador.y*multiplicando.y;
resultado.y=multiplicador.x*multiplicando.y+multiplicador.y*multiplicando.x;
return resultado;
}


union
Permite que una variable o estructura tenga cierta ambigüedad en su tipo. La forma de la instrucción es análoga a la de la estructura:
union nombre_union {tipos permitidos para la variable (con nombres)} variable(s);
Por ejemplo, union enteros {int ent; char car;}n  hace que n pueda tomar  valores de tipo entero o de tipo carácter. Para distinguir ambos casos se procede como para separar  las componentes de las estructuras, utilizando n.ent y n.car . Como en el caso de las estructuras el nombre_union puede emplearse para definir nuevas variables con una sintaxis como la anterior sin la parte entre llaves. Las uniones pueden aparecer en estructuras y matrices y viceversa.
    La ambigüedad en el tipo de la variable definida con union no permite que los valores correspondientes a los diferentes tipos puedan ser definidos independientemente. La variables  es internamente la misma y lo que varía es cómo se interpreta la información. Por ejemplo:


main(){
union entecaravect{ int ent; char car; char vec[3];} n;
n.ent=258; n.car=37;
n.vec[0]=5; n.vec[1]=11; n.vec[2]=75;
printf("Como ent %X\n", n.ent);
printf("Como car %X\n", n.car);
printf("Como vec [%X, %X, %X]\n\n", n.vec[0], n.vec[1], n.vec[2]);

n.vec[0]=5; n.vec[1]=11; n.vec[2]=75;
n.car=37; n.ent=258;
printf("Como ent %X\n", n.ent);
printf("Como car %X\n", n.car);
printf("Como vec [%X, %X, %X]\n\n", n.vec[0], n.vec[1], n.vec[2]);

union entecaravect m;
m.ent=254;
m.ent+=3;

printf("Como entero, 254+3 es %d.\n", m.ent);
printf("Como carácter, 254+3 es %d.\n", m.car);
}



Como ent 4B0B05
Como car 5
Como vec [5, B, 4B]

Como ent 102
Como car 2
Como vec [2, 1, 0]

Como entero, 254+3 es 257.
Como carácter, 254+3 es 1.






unsigned
Véase el comentario  en signed .

volatile
Según http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html  "indica al compilador que un objeto está sujeto a cambios repentinos por razones que no se pueden predecir a partir del programa mismos [procesos del sistema] y fuerza a que cada referencia a tal objeto sea una referencia genuina [no trata de optimizar el acceso al objeto]".



Estructuras recursivas y árboles

 Es posible  definir estructuras tales que uno de sus miembros sea un puntero a ella misma. Estas estructuras "autosemejantes" se pueden interpretar como árboles infinitos. Por ejemplo, una estructura que tenga dos miembros: un entero y (un puntero a) ella misma, podemos identificarla con una sucesión de enteros:

Estructura = entero, Estructura = entero, entero, Estructura = entero, entero, entero, Estructura = ...

En el caso en que los miembros fueran un entero y dos punteros a ella misma, se tendría un árbol binario. Cada uno de los punteros representa los subárboles que parten del entero. Cada vez que queramos introducir un dato en la dirección indicada hay que reservar espacio en memoria con malloc.

    El siguiente ejemplo es al que nos referimos en primer lugar,  obteniéndose una lista de enteros de longitud variable. A la derecha hay un esquema de la ordenación de los datos.

/* Crea una lista de enteros de longitud variable (esto se podría
hacer más fácilmente con malloc, creando una matriz de tamaño variable,
sin necesidad de estructuras recursivas). Para ello crea un árbol unario
*/

#include <stdio.h>
#include <stdlib.h>

typedef struct lista {
    int num; struct lista *rama; /* Estructura recursiva */
    } LISTA;

main(){
LISTA *comienzo; /* Comienzo de la lista */
LISTA *actual; /* Señala la posición en el árbol */
int longi, i, n;

printf("Longitud de la lista:\n");
scanf("%d",&longi);
comienzo=(LISTA*)malloc(sizeof(LISTA)); /* Reserva espacio */
actual=comienzo; /* Inicia el puntero actual */

for(i=0; i<longi; i++){
printf("Entero:\t");
scanf("%d",&n);
actual->num=n; /* Asigna el número */
actual->rama=(LISTA*)malloc(sizeof(LISTA)); /* Reserva espacio
                            para rama */
actual=(actual->rama); /* Baja en la rama */

} /* Fin del for */

actual->rama=NULL; /* Marca final de lista con NULL*/

/* --------- */

/* Imprime lista */
actual=comienzo;
while((actual->rama)){ /* Parar si la lista se acaba (si hay un NULL) */
printf("%d\n", actual->num);
actual=(actual->rama); /* Baja en la rama */
}/* Fin del while */

}


       
num







rama
 ->
num







rama
 ->
num







rama
 ->
num







rama
 ->



Podemos manipular estas listas borrando y añadiendo elementos redirigiendo los punteros correspondientes. Por ejemplo, para insertar el entero 444 en el cuarto lugar de una lista generada con el programa anterior, basta que insertemos el siguiente código en el lugar marcado con /* --------- */.

/* Insertar en el cuarto lugar un 444 */

if (longi>=4){ /* Para evitar listas cortas */
LISTA *inserto;
LISTA *transi; /* Sólo como buffer para guardar un puntero */
inserto=(LISTA*)malloc(sizeof(LISTA)); /* Reserva espacio para el
                     nuevo elemento */
inserto->num=444; /* El nuevo elemento será 444 */

actual=comienzo; /* Inicia el puntero actual */
for(i=1;i<3;i++) /*Bucle hasta el elemento anterior */
actual=(actual->rama);

transi=(actual->rama); /* Guardamos la dirección a la que apunta el
            tercer elemento, esto es, la del cuarto */
actual->rama=inserto; /*Hacemos que apunte a inserto */
inserto->rama=transi; /* y que inserto apunte donde antes apuntaba
            el tercer elemento */
}/*Fin del if*/


Lo que se hace es desconectar el tercer elemento del siguiente y redirigir el puntero al nuevo elemento.

    Se puede hacer una impresión recursiva, reemplazando la parte tras de /* Imprime lista */  simplemente por

imprimlista(comienzo); /* Imprime lista recursivamente *

donde imprilista es una función definida como:

void imprimlista(LISTA *tabla){
if ((tabla->rama)==NULL){
return;
}
imprimlista((tabla->rama));
}


Lo mismo se puede hacer con árboles en lugar de listas. Por ejemplo, el siguiente código crean un árbol  binario de tres niveles (raíz, hijos y nietos) formado por enteros y después los imprime recursivamente. Una definición recusiva de los elementos quizá podría abreviar bastante la primera parte. La impresión recursiva provoca que las ramas de la izquierda tengan prioridad sobre las de la derecha, y por tanto que el orden de impresión sea el indicado en el esquema de la derecha.

#include <stdio.h>
#include <stdlib.h>

typedef struct arbol {
    int num; struct arbol *ramd, *rami; /* Estructura recursiva */
    } ARBOL;

void imprimarbol(ARBOL*);

main(){
ARBOL *raiz; /* Comienzo del árbol */

raiz=(ARBOL*)malloc(sizeof(ARBOL)); /* Reserva espacio raíz */
(raiz->ramd)=(ARBOL*)malloc(sizeof(ARBOL)); /* Reserva espacio hijos */
(raiz->rami)=(ARBOL*)malloc(sizeof(ARBOL));
((raiz->ramd)->ramd)=(ARBOL*)malloc(sizeof(ARBOL)); /* Reserva espacio
                                 nietos */

((raiz->ramd)->rami)=(ARBOL*)malloc(sizeof(ARBOL));
((raiz->rami)->ramd)=(ARBOL*)malloc(sizeof(ARBOL));
((raiz->rami)->rami)=(ARBOL*)malloc(sizeof(ARBOL));

raiz->num=(rand() % 1000); /* Asigna raíz */
(raiz->ramd)->num=(rand() % 1000); /* Asigna espacio hijos */
(raiz->rami)->num=(rand() % 1000);
((raiz->ramd)->ramd)->num=(rand() % 1000); /* Asigna espacio nietos */
((raiz->ramd)->rami)->num=(rand() % 1000);
((raiz->rami)->ramd)->num=(rand() % 1000);
((raiz->rami)->rami)->num=(rand() % 1000);

/* Para indicar fin de árbol anula la rama derecha  de los nietos */
((raiz->ramd)->ramd)->ramd=NULL;
((raiz->ramd)->rami)->ramd=NULL;
((raiz->rami)->ramd)->ramd=NULL;
((raiz->rami)->rami)->ramd=NULL;


/* Imprime árbol recursivamente */
imprimarbol(raiz);
}

void imprimarbol(ARBOL *tabla){
printf("%d\n",tabla->num);
if ((tabla->ramd)==NULL){
return;
}
imprimarbol((tabla->rami));
imprimarbol((tabla->ramd));
}


Raíz

1









Hijos
2

5








Nietos
3

4

6

7





 5. Librerías

<ctype.h>
Funciones de comprobación de caracteres.

<math.h>
Operaciones matemáticas y algunas constantes. Las funciones matemáticas (excepto tres de ellas) son: sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, exp, log, log10, pow, sqrt, ceil, floor, fabs. Como ya se ha mencionado, las constantes M_PI y M_E son pi y e.  La lista de las constantes está en http://www.cs.cf.ac.uk/Dave/C/ .
    La función atan2(a,b) da el argumento entre -pi y pi del vector (b,a).
    Para compilar con gcc andemás del include correspondiente hay que escribir gcc fichero.c -lm para que enlace con la librería correspondiente.

<stdarg.h>
    Como ya hemos mencionado, es posible crear funciones con un número variable de argumentos usando esta librería, en la que va_list, va_start , va_end y va_arg son una especie de análogos de FILE , fopen, fclose y getc .

va_arg
Tiene dos argumentos, el primero es un puntero de tipo va_list y el segundo indica el tipo de parámetro al que apunta. Su efecto es obtener el valor de dicho parámetro.

va_end
La instrucción va_end(puntero) es la que indica el cierre de   va_start. Según parece, no hace nada en algunas  implementaciones ( http://www.cs.tut.fi/~leopold/Boar/AnsiLib/stdarg.html#va_end ).

va_list
Con va_list puntero se declara un tipo especial de puntero que apunta a una lista de argumentos.

va_start
La instrucción va_list(puntero,variable) provoca que puntero apunte al primer argumento trás de variable que debe ser el último argumento de la función verdaderamente declarado.

    Como ejemplo de una función con un número variable de argumentos,  consideremos el sumatorio, poniendo como primer argumento el número de sumandos:

#include <stdio.h>
#include <stdarg.h>

int sumatorio(int, ...);


main(){
printf("Suma de 2,3 y 6 = %d\n\n", sumatorio(3,2,3,6));
printf("Suma de 5,5, 7 y 6 = %d\n\n", sumatorio(4,5,5,7,6));
printf("Suma de 1,3,5,7,9,11 y 13 = %d\n\n", sumatorio(7,1,3,5,7,9,11,13));
}

int sumatorio(int num_de_arg, ...)
{
int i,j,resultado=0;

va_list punteroesp; /* Éste es el puntero especial */
va_start(punteroesp, num_de_arg); /*Ahora apunta al primer argumento
                después de num_de_arg*/
for(i=0; i<num_de_arg; i++){
j=va_arg(punteroesp,int); /*Extrae su valor y apunta al siguiente */
printf("El argumento %d es %d\n", i+1,j);
resultado=resultado+j;
}
va_end(punteroesp);
return resultado;
}





RESULTADO:



El argumento 1 es 2
El argumento 2 es 3
El argumento 3 es 6
Suma de 2,3 y 6 = 11

El argumento 1 es 5
El argumento 2 es 5
El argumento 3 es 7
El argumento 4 es 6
Suma de 5,5, 7 y 6 = 23

El argumento 1 es 1
El argumento 2 es 3
El argumento 3 es 5
El argumento 4 es 7
El argumento 5 es 9
El argumento 6 es 11
El argumento 7 es 13
Suma de 1,3,5,7,9,11 y 13 = 49





<stdio.h>
Entrada y salida. Incluye las siguientes funciones:

FILE
Sirve para declarar un puntero que señala a cierta estructura definida en la librería que representa un fichero.  Su uso es FILE *puntero .  Por defecto hay declarados ya tres punteros de este tipo (tres "corrientes"), que son: stdin , stdout y stderror .  El primero corresponde al teclado (entrada estándar) y los otros dos a la pantalla (salida estándar  y error estándar).

fclose
La instrucción fopen(puntero); cierra la comunicación con el fichero al que corresponde el puntero.

fopen
Se emplea con dos argumentos:  fopen(nombre,modo) donde nombre es el nombre de un fichero y modo es un modo de acceso, "r" para lectura, "w " para escritura y "a" para escribir añadiendo. El resultado de fopen es un puntero de los declarados con FILE correspondiente al fichero del nombre indicado, que queda abierto en el modo indicado. Si se elige la opción "r+" se podrá no sólo leer, sino también escribir en el fichero (pero no crear uno nuevo). Cuando se alternan lectura y escritura, hay que tener en cuenta que el acceso es secuencial (¿por medio de un puntero que no vemos pero del que tenemos información con ftell?) y la escritura provoca el cambio del carácter en curso. Por ejemplo, supongamos un fichero llamado datos.dat cuyo contenido es  1234567890. La acción del siguiente programa sería la indicada en la derecha:

#include <stdio.h>
#include <stdlib.h>

main(){
FILE *puntero;
int i;
char c;
puntero=fopen("datos.dat","r+");

for(i=1;i<=10; i++){
putc('x',puntero);
getc(puntero);
}
fclose(puntero);

/*Esto es como cat */
puntero=fopen("datos.dat","r");
i=0;
printf("Nuevo fichero datos.dat:\n");
while((c=getc(puntero))!=EOF){
printf("Carácter %d = %c\n",++i,c);
}
printf("\n");
}

Nuevo fichero datos.dat:
Carácter 1 = x
Carácter 2 = 2
Carácter 3 = x
Carácter 4 = 4
Carácter 5 = x
Carácter 6 = 6
Carácter 7 = x
Carácter 8 = 8
Carácter 9 = x
Carácter 10 = 0
Carácter 11 = x
Carácter 12 = x
Carácter 13 = x
Carácter 14 = x
Carácter 15 = x



Si el fichero datos.dat no existiera, habría un error de "violación de segmento" y si sustituyéramos "r+" por "r" , no se podría efectuar la escritura y el fichero quedaría igual. Con "a" o "a+" en lugar de "r+" , se añadirían  diez 'x' al final del fichero, mientras que con "w" o "w+", el fichero estaría formado por estas diez 'x'.

fprint
Funciona igual que printf salvo que tiene un primer argumento extra que es un puntero de tipo FILE. Por ejemplo, fprintf(stdout,"hola\n"); equivale a printf("hola\n").

fscanf
Corresponde a scanf de la misma forma que fprintf corresponde a printf.

fseek
Permite un acceso aleatorio del fichero en lugar de secuencial. Tiene tres formas: fseek(puntero, offset, SEEK_SET) fseek(puntero, offset, SEEK_CUR) y fseek(puntero, offset, SEEK_END)  donde puntero es un puntero de tipo FILE y offset es un entero (de tipo long). Su efecto es hacer que el nuevo caracter en curso difiera offset unidades del principio del fichero o del actual carácter en curso o del fin del fichero (en el último caso el offset debe ser negativo. Por ejemplo, supongamos como antes un fichero llamado datos.dat cuyo contenido es  1234567890, entonces el programa :

#include <stdio.h>

main(){
FILE *puntero;
puntero=fopen("datos.dat","r");
fseek(puntero,2,SEEK_CUR);
printf("%c\t", getc(puntero));
fseek(puntero,2,SEEK_SET);
printf("%c\t", getc(puntero));
fseek(puntero,-2,SEEK_END);
printf("%c\t", getc(puntero));
fseek(puntero,0,SEEK_CUR);
printf("%c\t", getc(puntero));
fseek(puntero,4,SEEK_SET);
printf("%c\n", getc(puntero));
fclose(puntero);
}

tendría  como salida: 3       3       9       0       5.



ftell
Su argumento es un puntero de tipo FILE y devuelve el número de carácter en curso . Por ejemplo, tras fseek(puntero, 0, SEEK_END) ,  ftell(puntero) da la longitud del fichero en bytes .

getc
Es igual que getchar pero con un argumento de tipo puntero FILE. concretamente getchar() equivale a getc(stdin).

putc
Es como putchar con un argumento extra de tipo puntero FILE que va al final. Así pues putchar(c) equivale a putc(c,stdout) .

Ejemplos:
    El primer programa pasa los  acentos de abc.dat a formato TeX, escribiendo el resultado en nuev.dat. El segundo simula el comando cat de linux (K & R p.163).

main(){
    char ci;
    FILE *ifp, *ofp;
    ifp=fopen("abc.dat","r");
    ofp=fopen("nuev.dat","w");
  
        while ((ci=getc(ifp))!=EOF){  
            switch (ci){
            case 'á': fprintf(ofp,"%s","\\'{a}");
            break;
            case 'é': fprintf(ofp,"%s","\\'{e}");
            break;
            case 'í': fprintf(ofp,"%s","\\'{\\i}");
            break;
            case 'ó': fprintf(ofp,"%s","\\'{o}");
            break;
            case 'ú': fprintf(ofp,"%s","\\'{u}");
            break;
            case 'Á': fprintf(ofp,"%s","\\'{A}");
            break;
            case 'É': fprintf(ofp,"%s","\\'{E}");
            break;
            case 'Í': fprintf(ofp,"%s","\\'{I}");
            break;
            case 'Ó': fprintf(ofp,"%s","\\'{O}");
            break;
            case 'Ú': fprintf(ofp,"%s","\\'{U}");
            break;
            case 'ñ': fprintf(ofp,"%s","\\~{n}");
            break;
            case 'Ñ': fprintf(ofp,"%s","\\~{N}");
            break;
            case 'ü': fprintf(ofp,"%s","\\\"{u}");
            break;
            case '¿': fprintf(ofp,"%s","?\\lq ");
            break;
            case '¡': fprintf(ofp,"%s","!\\lq ");
            break;
            default:
                fprintf(ofp,"%c",ci);
            }
        }
}
main(int argc, char *argv[]){
FILE *fp;
void filecopy(FILE *,FILE *);
char *prog=argv[0];

if (argc==1) /* copia a pantalla si no hay argumentos */
    filecopy(stdin,stdout);
else
    while (--argc>0)
        if((fp=fopen(*++argv,"r"))==NULL){
        fprintf(stderr, "%s: No puedo abrir %s\n", prog, *argv);
        exit(1);
        }
        else{
        filecopy(fp,stdout);
        fclose(fp);
        }
if (ferror(stdout)){
    fprintf(stderr, "%s: Error de escritura\n", prog);
    exit(2);
}
exit(0);
}

void filecopy(FILE *ifp, FILE *ofp){
int c;
while((c=getc(ifp))!=EOF)
    putc(c,ofp);
}



rewind
Se sitúa al comienzo del fichero indicado por  el puntero FILE indicado por su único argumento.


<stdlib.h>
Utilidades generales. Define, entre otras,  las siguientes funciones para la gestión dinámica de la memoria:

calloc
Es igual que malloc  pero ahora con dos argumentos enteros, el primero indica el número de datos que queremos guardar en el espacio reservado, y el segundo indica cuántos bytes ocupa cada uno.

free
La instrucción free(puntero) libera el espacio de memoria al que apunta un puntero (creado con malloc  o calloc) para que se pueda reutilizar.

malloc
Es una función que dado un número entero , n, reserva n bytes de memoria y devuelve un puntero que apunta al primero. Si el puntero es nulo (NULL) es que la operación no ha podido llevarse a cabo con éxito.

Por ejemplo, si quisiéramos una matriz de dimensión variable podríamos utilizar el siguiente programa:

main(){
int i,n;
char *p;

printf("Introduce dimensión de la matriz.\n");
scanf("%d",&n);

p=(char *)malloc(n);

printf("Introduce los datos (enteros [0,255])\n");
for(i=0;i<n;++i) scanf("%d",p++);

printf("Los datos introducidos son en orden inverso:\n");
for(i=n;i>0;--i) printf("a[%d]= %d\n",i-1,*(--p));

free(p);
}

    El paréntesis (char *) antes de malloc es necesario para indicar al compilador de cuánto en cuánto se incrementa el puntero.
   

    Además esta librería contiene otras funciones:

atoi
Permite "quitar las comillas" en una cadena de carcteres que representa un entero, por ejemplo, atoi("211") es 211.

atoi
Lo mismo que atoi  pero en coma flotante.

rand
Al ejecutar rand() se devuelve un número aleatorio que en la máquina en uso parece estar entre 0 y 2^31-1.


srand
Cambia la semilla del generador de números aleatorios. Es normal conectarlo con el reloj del ordenador con la instrucción:
srand( (unsigned int)time(NULL));


atexit
Es una función que tiene como argumento un puntero de una función y provoca que ésta se ejecute si el programa finaliza normalmente.

exit
Su efecto es terminar la ejecución del programa. lleva un argumento entero que puede informar al sistema de si ha habido un error (cero corresponde a una finalización sin problemas).


<string.h>
Operaciones con cadenas de caracteres.

strlen
Halla la longitud de una cadena (sin contar el carácter NULL final).

strcpy
Tiene dos argumentos que son (punteros a) cadenas, y copia el segundo en el primero. Según el libro de R.L. Petersen "es una de las funciones más peligrosas usadas en C". La razón es que si las longitudes no coinciden entonces la copia puede sobreescribir el valor de otras variables en memoria. Pero los ejemplos problemáticos que se mencionan allí no parecen actuar en la máquina en uso.

strcmp
Compara cadenas. El resultado es un entero positvo si la primera es mayor , y negativo si la primera es menor. Es cero en caso de igualdad. El valor absoluto de esta función parece indicar la distancia entre las cadenas.

strcat
Añade una cadena a otra.

Un ejemplo de todo esto es:

#include <string.h>

main(){
char cadena1[6]="Hola ";
char cadena2[6]="Allo ";
char cadena3[]=" amigos";
printf("Las longitudes de las cadenas \"%s\", \"%s\" y \"%s\" son:\t", cadena1, cadena2,cadena3);
printf("%d, %d y %d \n",strlen(cadena1),strlen(cadena2),strlen(cadena3));

printf("Se cumple \"%s\"<\"Homa\" porque strcmp(cadena1,\"Homa\")=%d\n",  cadena1,strcmp(cadena1,"Homa"));

printf("Antes de strcpy(cadena2,cadena1), se tiene cadena2=%s\n",cadena2);
strcpy(cadena2,cadena1),
printf("Después de strcpy(cadena2,cadena1), se tiene cadena2=%s\n",cadena2);
strcat(cadena1,cadena3);
printf("El efecto de strcat(cadena1,cadena3) es que cadena1=%s\n", cadena1);
}

cuyo resultado es:

Las longitudes de las cadenas "Hola ", "Allo " y " amigos" son: 5, 5 y 7
Se cumple "Hola "<"Homa" porque strcmp(cadena1,"Homa")=-1
Antes de strcpy(cadena2,cadena1), se tiene cadena2=Allo
Después de strcpy(cadena2,cadena1), se tiene cadena2=Hola
El efecto de strcat(cadena1,cadena3) es que cadena1=Hola  amigos


<time.h>
Funciones relacionadas con el tiempo.  Incluye entre otras funciones:

clock
El tiempo de procesador transcurrido desde el comienzo de la ejecución del programa es clock(). Es un entero ¿milésimas de segundo?

time
Su argumento es un puntero. En el caso particular time(NULL) da el tiempo en segundos desde el uno de enero de 1970.



 6. Compilador

Un error muy difícil de detectar que el compilador puede no señalar, viene dado por escribir +/ en vez de */ al cerrar un comentario. El compilador unirá esa línea a la siguiente haciendo que sea invisible.

El compilador se queja si uno no añade una línea al final del fichero fuente.

Cuando se ejecuta un compilador de C, se efectúan tres operaciones: el preprocesado, el compilado propiamente dicho y el enlace. La primera realiza algunas simplificaciones previas, la segunda crea el código objeto y la tercera los une en un solo ejecutable. Si se parte de código objeto en vez de código fuente, los dos primeros pasos no se llevan a cabo. En el gcc y el cc, la opción -c obliga a que sólo se genere el código objeto.

En memoria un programa en C consta principalmente de cuatro segmentos, el primero contiene las instrucciones y el sistema operativo impide que se pueda acceder a esa parte de la memoria (con punteros). El segundo contien las variables estáticas y globales que permanecen durante la ejecución del programa. El tercero está libre y es donde actúa malloc. El cuarto es el stack donde se almacenan variables temporales y datos de las llamadas a funciones.


Inclusión de librerías

Un programa "vacío", esto es, main(){},  procesado con gcc, cc y g++ ocupa respectivamente 10991, 10991 y 11111 bytes. ¿Qué librerías cargan o enlazan?  Se puede reducir mucho el tamaño de un programa vacío (hasta 778 bytes en la máquina en curso) usando las opciones del compilador gcc:

/*
Compilar con gcc realempty.c -nostartfiles -nodefaultlibs -nostdlib -lgcc
Se recibe un aviso, pero crea un ejecutable de 778 bytes
Parece que gcc realempty.c -nostdlib -lgcc
es similar (ver http://gcc.gnu.org/onlinedocs/gcc-3.2.3/gcc/Link-Options.html )
*/
int main(){
}

Ahora parece que el código generado no se deja ejecutar.

Algunas librerías como <math.h>  aparentemente no funcionan con gcc  sin embargo  con g++ sí. La manera de remediarlo es escribir gcc  fichero.c -lm.
En las FAQ de http://www.faqs.org/faqs/C-faq/faq/ se puede leer:

11.30:     Why are some ANSI/ISO Standard library functions showing up as undefined, even though I've got an ANSI compiler?
A:     It's possible to have a compiler available which accepts ANSI syntax, but not to have ANSI-compatible header files or run-time libraries installed. (In fact, this situation is rather common when using a non-vendor-supplied compiler such as gcc.) See also questions 11.29, 13.25, and 13.26.

13.25:     I keep getting errors due to library functions being undefined, but I'm #including all the right header files.

A:     In general, a header file contains only declarations. In some cases (especially if the functions are nonstandard) obtaining the actual *definitions* may require explicitly asking for the correct libraries to be searched when you link the program. (#including the header doesn't do that.) See also questions 11.30, 13.26, and 14.3.

13.26:     I'm still getting errors due to library functions being undefined, even though I'm explicitly requesting the right libraries while linking.

A:  Many linkers make one pass over the list of object files and libraries you specify, and extract from libraries only those modules which satisfy references which have so far come up as undefined. Therefore, the order in which libraries are listed with respect to object files (and each other) is significant; usually, you want to search the libraries last. (For example, under Unix, put any -l options towards the end of the command line.) See also question 13.28.


13.28:     What does it mean when the linker says that _end is undefined?

A:     That message is a quirk of the old Unix linkers. You get an error about _end being undefined only when other symbols are undefined, too -- fix the others, and the error about _end will disappear. (See also questions 13.25 and 13.26.)

14.2:     I'm trying to take some square roots, but I'm getting crazy numbers.

A:     Make sure that you have #included <math.h>, and correctly declared other functions returning double. (Another library function to be careful with is atof(), which is declared in <stdlib.h>.) See also question 14.3 below. References: CT&P Sec. 4.5 pp. 65-6.

14.3:     I'm trying to do some simple trig, and I am #including <math.h>, but I keep getting "undefined: sin" compilation errors.

A:     Make sure you're actually linking with the math library. For instance, under Unix, you usually need to use the -lm option, at the *end* of the command line, when compiling/linking. See also questions 13.25, 13.26, and 14.2.
 


  Preprocesado

   El preprocesador actúa en la primera parte de la compilación principalmente a través de #include y #define que no necesitan  punto y coma al final. Cuando encuentra  #include "fichero"  o  #include <fichero> , el efecto es análogo a reemplazar esa línea con el código del fichero en cuestión. En el primer caso comienza buscando el fichero en el directorio del programa fuente y si no lo encuentra, busca después en ciertos sitios predeterminados. En el segundo caso, sólo busca en los sitios predeterminados (que dependen del sistema). Normalmente los ficheros incluidos tiempen prototipos de funciones y declaraciones de variables y estructuras. Aparentemente si sólo tienen prototipos de funciones basta incluirlos en el fichero principal que llama a todas las funciones, sin embargo las definiciones de estructuras y variables pueden ser necesarias en todas las funciones.
    Por otra parte, #define sirve para crear abreviaturas, nombres alternativos o macros. Su estructura es #define alias texto. Por ejemplo,   #define pi 3.1416 provoca que el preprocesador escriba 3.1416 en todos los sitios en los que pi funciona como palabra clave (no dentro de una cadena de caracteres). El preprocesador efectúa las simplificaciones que puede, así con #define pim (3.1416/2) no se sustituirá pim por (3.1416/2) si no por 1.5708 .  Por ejemplo:

#define pi 3.1416
#define pim (3.1416/2)

main(){
printf("Aproximación de pi %f y pi medios %f\n",pi,pim);
}

imprime: Aproximación de pi 3.141600 y pi medios 1.570800

Se pueden hacer definiciones tan largas como se quiera  escribiendo \ al final de cada línea.
Hay cinco definiciones que hace el compilador cuando se ejecuta, recogidas en la siguiente tabla:

__DATE__
Cadena de caracters con la fecha
__FILE__
Cadena de caracters con el nombre del fichero
__TIME__
Cadena de caracters con la hora
__LINE__
Entero con el número de línea
__STDC__
Entero no nulo si se cumplen las normas ANSI
Por ejemplo:

main(){
printf("La fecha de hoy es: %s\n",__DATE__);
printf("La hora es: %s\n",__DATE__);
printf("Esta línea es la número %d\n",__LINE__);
printf("El fichero es: %s\n",__FILE__);
}


También se puede emplear  #define para crear nuevas instrucciones (macros) a partir de otras conocidas. Simplemente se escriben letras arbitrarias en lugar de los argumentos. Esto puede dar lugar a resultados un poco inesperados. Por ejemplo, el primer programa parece análogo al segundo salvo que éste emplea una función en lugar de #define :

#define cubo(a) a*a*a

main(){
int i=2;
printf("%d\n", cubo(i+1));
}
#include <stdio.h>
int cubo (int);
main(){
int i=2;
printf("%d\n", cubo(i+1));
}
int cubo(int j){
return (j*j*j);
}


Sin embargo el resultado del primero es 2+1*2+1*2+1=7 y el del segundo (2+1)*(2+1)*(2+1)=27 .
Dentro de cada #define se puede utilizar # para transformar una expresión en una cadena de caracteres y ## para pegar dos expresiones. Por ejemplo,

#define imprvar(a) printf("La variable " #a " vale %d\n",a)
#define pega(a,b) a##b

main(){
int i=1,j=2,ij=3;
imprvar(i);
imprvar(j);
imprvar(pega(i,j));
}

da lugar a
La variable i vale 1
La variable j vale 2
La variable pega(i,j) vale 3

El preprocesador también admite cierto tipo de instrucciones if  que se pueden combinar con  la expresión defined que indica si un nombre está definido o no. Por ejemplo (K & R p.91), si un fichero cabecera.h  es incluido en diferentes partes de un programa y sólo queremos que lo haga  la primera (para no desperdiciar memoria), se puede sustituir cabecera.h   por

#if !defined(FLAG)
#define FLAG

(código de cabecera.h)

#endif

La primera vez que se acceda a cabecera.h , FLAG quedará definido y el ciclo if saltará el código el resto de las veces.
También se puede utilizar #if para que ciertas instrucciones de comprobación se compilen sólo si así lo queremos. Por ejemplo, si en algún punto de un programa incluimos:
#if defined(FASE_DE_PRUEBAS)
printf("Comprueba valor de a=%d",a);
#endif
si en la primera línea escribimos #define FASE_DE_PRUEBAS entonces se compilará la instrucción que imprime la variable, y si  la omitimos no se compilará.

Otra aplicación es que en la primera línea de un programa principal podemos incluir #define SISTEMA (tipo de sistema) y entonces el siguiente ciclo podría elegir la cabecera adecuada según el sistema operativo:

#if SISTEMA==LINUX
    #include "cabeceralinux.h"
#elif SISTEMA==MSDOS  
    #include "cabeceramsdos.h"
#else
    #include "cabeceraotros.h"
#endif



Código en varios ficheros
Cuando el código de un programa está dividido en varios ficheros, los prototipos de las funciones resultan cruciales. Por ejemplo, si en tres ficheros ponemos el código:

float suma(float, float);
float producto(float, float);

main(){
float x,y;
printf("Primer número:\n");
scanf("%f",&x);
printf("Segundo número:\n");
scanf("%f",&y);
printf("Suma:%f\n",suma(x,y));
printf("Producto:%f\n",producto(x,y));
}

float suma(float a,float b){
return a+b;
}


float producto(float a,float b){
return a*b;
}


y lo compilamos con gcc fichero1.c fichero2.c fichero3.c ,  un ejemplo del resultado será el mostrado en la siguiente columna de la izquierda, pero si omitimos el primer portotipo de la función  o ambos, en la máquina en curso se obtienen los resultados erróneos de las otras dos columnas:

Primer número:
1.1
Segundo número:
2.3
Suma:3.400000
Producto:2.530000
Primer número:
1.1
Segundo número:
2.3
Suma:-1.997368
Producto:2.530000
Primer número:
1.1
Segundo número:
2.3
Suma:-1.997368
Producto:-1.997368


En el primer fichero también se pueden meter los dos prototipos de función en un fichero, digamos  cabecera.h y sustituirlas por   #include "cabecera.h".

Las variables (o funciones) definidas fuera del bloque de una función (externas) tienen validez desde la línea en que se declaran hasta el final del fichero en que están incluidas. Por ello es conveniente que las cabeceras con declaraciones y  prototipos estén al comienzo.


La utilidad make
Si un programa está distribuido en muchos ficheros es conveniente emplear la utilidad make. Se puede crear un fichero con el nombre Makefile conteniendo las instrucciones de compilado y al ejecutar el comando make se llevarán a cabo. La utilidad make tiene un lenguaje de programación propio  (véase http://www.math.utah.edu/docs/info/make_1.html y http://www.math.utah.edu/docs/info/make-stds_1.html ).  La estructura básica  de las instrucciones más simples es:

fichero_objetivo: ficheros_requisitos
    comandos

Necesariamente antes de comandos hay dejar una tabulación. El efecto de líneas de este tipo es que si existen y están actualizados  los ficheros requisitos se efectúan los comandos. Si no fuera así se busca una línea donde lo que falla sea un objetivo. Por ejemplo, si tenemos cuatro  ficheros ejem1.c ejem2.c ejem3.c ejem4.c y digamos que ejem1.c es el principal y los otros contienen las funciones que se emplean en él.  La instrucción para el compilado sería gcc -o a.out ejem1.o ejem2.o ejem3.o ejem4 .o si ya tuviéramos los códigos objetos de los ficheros. En Makefile podemos poner como requisito ejem1.o ejem2.o ejem3.o ejem4.o . Si  por ejemplo ejem4.o no estuviera actualizado  se buscaría una línea en que es objetivo, dicha línea debería exigir como requisito el  fichero ejem4.c con el código fuente, y el comando correspondiente debería ser
gcc -c ejem4.c. en definitiva, en esta situación podríamos hacer un Makefile del tipo:

##############
# Makefile
##############

# Enlaza los códigos objetos
a.out: ejem1.o ejem2.o ejem3.o ejem4.o
        gcc -o a.out ejem1.o ejem2.o ejem3.o ejem4.o
        echo Make completado

# Compila los códigos fuente
ejem1.o: ejem1.c
        gcc -c ejem1.c
ejem2.o: ejem2.c
        gcc -c ejem2.c
ejem3.o: ejem3.c
        gcc -c ejem3.c
ejem4.o: ejem4.c
        gcc -c ejem4.c


Los caracteres # indican comienzos de una línea de comentarios. Por otra parte, echo sirve para imprimir en pantalla. Otros comandos Linux/UNIX que funcionan dentro de Makefile, según la documentación, son:
cat cmp cp egrep expr grep ln mkdir mv pwd rm rmdir sed test touch
En el Makefile se pueden incluir variables arbitrarias. Hay también algunas de carácter especial. algunas de ellas son

Variable
Significado
$@
El nombre del fichero objetivo.
$?
Ficheros requisitos más recientes que el objetivo .
$<
Igual que  $? con reglas de sufijos (ver más adelante).
$*
Igual que $@ con reglas de sufijos (ver más adelante).
$+
Todos los ficheros requisitos separados por espacios.
%
Uno o varios caracteres.

Podemos definir otras variables con variable = valor y referirnos a ellas con $(variable).  Normalmente se llama  CC al compilador y CFLAGS a las opciones del compilador. Poir ejemplo, el Makefile anterior se podría reducir a:

####################
# Makefile abreviado
####################

CC = gcc
OBJECTS = ejem1.o ejem2.o ejem3.o ejem4.o

# Enlaza los códigos objetos
a.out: $(OBJECTS)
        $(CC) -o $@ $+
        echo Make completado

# Compila los códigos fuente
%.o: %.c
        $(CC) -c $+

Las reglas de sufijos son una forma alternativa de escribir %.ext1: %.ext2, simplemente sustituyéndolo por .est1.ext2: En el ejemplo anterior, las dos últimas líneas podrían ser:

.c.o:
        $(CC) -c $<



Se puede añadir a cierta variable una expresión empleando "+=" (como en C). Esto podría ser útil cuando hay que definir una variable de varias líneas, aunque para ello también es posible emplear el carácter "\".

    Un ejemplo de Makefile para compilar cierto código que emplea SDL es:

##############
# Mi makefile
##############

# Compila  con gcc
CC=gcc
OBJECTS = perse.o leetec.o inipan.o creani.o colisi.o mupin.o
OBJECTS += muer.c
## Para nueva línea también se podría usar "\"
INC_DIRS = /usr/include/SDL
LIBS = -lSDL -lpthread -lSDL_image -lSDL_mixer
FLAGS = -D_REENTRANT  -Wl,-rpath

##########################
## sdl-config --cflags
## produce:  -I/usr/include/SDL -D_REENTRANT
## y sdl-config --libs
## -L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lpthread
##########################


# Enlaza los códigos objetos
a.out: $(OBJECTS)
        $(CC) -o $@ $+ $(FLAGS) -I$(INC_DIRS)  $(LIBS)

# Compila los códigos fuente

%.o: %.c cabecera.h
        $(CC) -c $< -I$(INC_DIRS)