cc31a Desarrollo de Software de Sistemas

Patricio Poblete (ppoblete@dcc.uchile.cl)

Introducción al Lenguaje C

  1. Un primer programa
  2. Entrada/salida de caracteres
  3. Arreglos
  4. Funciones
  5. Arreglos de caracteres
  6. Variables externas
  7. Conversiones de caracteres
  8. Operaciones con bits
  9. Prioridad de Operadores
  10. Instrucciones de Control
  11. Punteros y Arreglos
  12. Memoria Dinámica
  13. Argumentos para el programa
  14. Estructuras
  15. Typedef
  16. Uniones
  17. Entrada/Salida
  18. setjmp/longjmp
  19. Punteros a funciones
  20. Funciones con un número variable de parámetros
  21. La Biblioteca Standard de Entrada/Salida

Un primer programa

El siguiente es un programa que al ejecutarse escribe Hola en la pantalla:

hola.c


        #include <stdio.h>
        
        main()
          {
            printf("Hola\n");
          }
Para compilar y ejecutar este programa se utilizan los comandos siguientes:


            % cc hola.c
            % ./a.out

Ejemplo: Conversión Fahrenheit-Celsius (C=(5/9)*(F-32))

fahrenheit.c


        #include <stdio.h>
        
        /* Imprime tabla Fahrenheit-Celsius */
        
        main()
          {
            int f, c;
            int min, max, step;
        
            min=0;
            max=300;
            step=20;
        
            f=min;
            while(f<=max)
              {
                c=5*(f-32)/9;
                printf("%d\t%d\n", f, c);
                f=f+step;
              }
          }

Versión más compacta:


        for(f=min; f<=max; f+=step)
            printf("%d\t%d\n", f, 5*(f-32)/9);

Tipos de datos:


    int
    float
    char
    short
    long
    double

Para tener más precisión podemos declarar f como float:


        float f;
        
        for(f=min; f<=max; f+=step)
            printf("%3.0f %6.1f\n", f, (5.0/9.0)*(f-32.0));

Constantes simbólicas:


        #define MIN 0
        #define MAX 300
        #define STEP 20
        
        for(f=MIN; f<=MAX; f+=STEP)
            . . .

Reuniendo todo lo anterior, tenemos la siguiente versión para el programa:

fahrenheit2.c


        #include <stdio.h>
        
        #define MIN 0
        #define MAX 300
        #define STEP 20
        
        /* Imprime tabla Fahrenheit-Celsius */
        
        main()
          {
            float f;
        
            for(f=MIN; f<=MAX; f+=STEP)
                printf("%3.0f %6.1f\n", f, (5.0/9.0)*(f-32.0));
          }

Entrada/salida de caracteres


        int c; /* debe declararse int, no char */
        
        c = getchar();  /* lee desde la entrada standard */
                        /* retorna EOF al llegar al fin de archivo */
        putchar(c);

Ejemplo: Copiar entrada hacia salida

copia.c


        #include <stdio.h>
        
        /* Copiar entrada hacia salida */
        
        main()
          {
            int c;
        
            c=getchar();
            while(c!=EOF)
              {
                putchar(c);
                c=getchar();
              }
          }

Versión más compacta:

copia2.c


        #include <stdio.h>
        
        /* Copiar entrada hacia salida */
        
        main()
          {
            int c;
        
            while((c=getchar())!=EOF)
                putchar(c);
          }

Hay que tener cuidado con la parentización. Lo siguiente


        while(c=getchar() != EOF)

estaría mal, porque el operador != tiene mayor prioridad que la asignación.

Uso:


        % ./a.out
                             Lee del teclado y escribe en pantalla

        % ./a.out <arch1
                             Copia de arch1 a la pantalla

        % ./a.out <arch2 >arch2
                             Copia de arch1 a arch2

        % ./a.out >arch2
                             Guarda en arch2 lo que se tipea en el teclado

Ejemplo:

cuentac.c


        /* Cuenta caracteres */
        
        #include <stdio.h>
        
        main()
          {
            int nc;
        
            nc=0;
            while(getchar()!=EOF)
                ++nc;
        
            printf("%d\n", nc);
          }

Ejemplo:

cuental.c


        /* Cuenta líneas */
        
        #include <stdio.h>
        
        main()
          {
            int c, nl;
        
            nl=0;
            while((c=getchar())!=EOF)
                if(c=='\n')
                    ++nl;
        
            printf("%d\n", nl);
        }

Otras instrucciones:


              if(    )                  if(    )
                {                         {
                  . . .                     . . .
                }                         }
                                        else
                                          {
                                            . . .
                                          }

Arreglos

Ejemplo: Contar cuantas veces aparece cada dígito.

Para esto, llevamos un conjunto de 10 contadores, para cada uno de los dígitos. Esto es un arreglo aen donde el contador asociado al dígito c es a[c-'0'].

cuentad.c


        /* Cuenta dígitos */
        
        #include <stdio.h>
        
        main()
          {
            int c, i;
            int a[10];
        
            for(i=0; i<=9; ++i)
                a[i]=0;
        
            while((c=getchar())!=EOF)
                if(c>='0' && c<='9')
                    ++a[c-'0'];
        
            for(i=0; i<=9; ++i)
                printf("%d    %d\n", i, a[i]);
          }

Funciones

Ejemplo: Elevación a potencia (cálculo de an)

potencia.c


        #include <stdio.h>
        
        /* Función que calcula elevación a potencia */
        int potencia(int a, int n)
          {
            int i, p;
        
            p=1;
            for(i=1; i<=n; ++i)
                p*=a;
        
            return p;
          }
        
        /* Programa de prueba que imprime potencias de 2 y de -3 */
        main()
          {
            int i;
        
            for(i=0; i<=10; ++i)
                printf("%2d %6d %6d\n",
                    i, potencia(2,i), potencia(-3,i));
        
            return 0; /* para indicar fin OK */
          }

El traspaso de parámetros es por valor. Los parámetros son variables locales y pueden ser modificados sin problema.


        int potencia(int a, int n)
          {
            int p;
        
            for(p=1; n>0; --n)
                p*=a;
        
            return p;
          }

Arreglos de caracteres

Para almacenar un string se usa un arreglos de chars, marcando el fin del string con un caracter nulo ('\0'). Una constante de tipo string ("...") tiene automáticamente un caracter nulo al final.

Ejemplo: Encontrar el largo de la línea más larga

maxlinea.c


        #include <stdio.h>
        
        #define MAXLINEA 1000
        
        /* Lee una linea en arreglo s de largo máximo lim */
        /* Retorna 0 si encuentra fin de archivo */
        int getline(char s[], int lim)
          {
            int c, i;
        
            for(i=0; i<lim-1
                  && (c=getchar())!=EOF
                  && c!='\n'; ++i)
                s[i]=c;
            if(c=='\n')
              {
                s[i]=c;
                ++i;
              }
            s[i]='\0';
        
            return i;
          }
        
        /* Programa que imprime el largo de la línea más larga */
        main()
          {
            int largo, maxlargo;
            char linea[MAXLINEA];
        
            maxlargo=0;
            while((largo=getline(linea, MAXLINEA))>0)
                if(largo>maxlargo)
                    maxlargo=largo;
        
            printf("La línea más larga tiene %d caracteres\n", maxlargo);
          }
¿Cómo imprimir la línea más larga?

Para recordar la línea más larga no basta con hacer


        char larga[MAXLINEA];
        
        ...
        
        if(largo>maxlargo)
          {
            maxlargo=largo;
            larga=linea;              /* ERROR */
          }
porque esto no copia los caracteres. En lugar de ello, habría que llamar a una función que copia los caracteres uno por uno:

Ejemplo: Encontrar la línea más larga

maxlinea2.c


        #include <stdio.h>
        
        #define MAXLINEA 1000
        
        /* Copia desde -> hacia */
        void copia(char hacia[], char desde[])
          {
            int i;
        
            for(i=0; (hacia[i]=desde[i])!='\0'; ++i)
                ;
          }
        
        /* Lee una linea en arreglo s de largo máximo lim */
        /* Retorna 0 si encuentra fin de archivo */
        int getline(char s[], int lim)
          {
            int c, i;
        
            for(i=0; i<lim-1
                  && (c=getchar())!=EOF
                  && c!='\n'; ++i)
                s[i]=c;
            if(c=='\n')
              {
                s[i]=c;
                ++i;
              }
            s[i]='\0';
        
            return i;
          }
        
        /* Programa que imprime la línea más larga */
        main()
          {
            int largo, maxlargo;
            char linea[MAXLINEA], larga[MAXLINEA];
        
            maxlargo=0;
            while((largo=getline(linea, MAXLINEA))>0)
                if(largo>maxlargo)
                  {
                    maxlargo=largo;
                    copia(larga, linea);
                  }
        
            printf("La línea más larga es:\n%s\n", larga);
          }

En realidad hay funciones predefinidas para realizar algunas de estas operaciones de strings.

En lugar de getline se usa


        char[] fgets(char s[], int max, FILE *stream)
Al llamar a esta función, si queremos que lea de su entrada standard, hay que invocarla con stdin como tercer parámetro.

Las siguientes funciones requieren incluir un nuevo archivo de encabezamiento:


        #include <string.h>
En lugar de copia se usa

        char[] strcpy(char to[], char from[])
y para calcular el largo de un string se puede usar

        int strlen(char s[])
Ejemplo: Imprime la línea más larga.

maxlinea3.c


        /* Programa que imprime la línea más larga */
        
        #include <stdio.h>
        #include <string.h>
        
        #define MAXLINEA 1000
        
        main()
          {
            int largo, maxlargo;
            char linea[MAXLINEA], larga[MAXLINEA];
        
            maxlargo=0;
            while(fgets(linea, MAXLINEA, stdin)!=NULL)
              {
                largo=strlen(linea);
                if(largo>maxlargo)
                  {
                    maxlargo=largo;
                    strcpy(larga, linea);
                  }
              }
        
            printf("La línea más larga es:\n%s\n", larga);
          }

Variables externas

Las variables declaradas fuera de las funciones son almacenadas estáticamente y pueden ser accesadas desde las funciones.

Ejemplo:


        int x;
        
        f()
          {
            extern x;
        
            /* aquí se puede usar x */
          }
La declaración dentro de la función se puede omitir si la declaración externa está dentro del mismo archivo.

Conversiones de caracteres

Al incluir el archivo


        #include <stdlib.h>
se pueden utilizar funciones como atoi(s), que convierte un string a entero ("ascii to int"), atol(s) ("ascii to long"), etc.

La siguiente es una implementación posible de atoi:


        /* atoi: convierte string ascii a entero */
        int atoi(char s[])
          {
            int i, n;
        
            n=0;
            for(i=0; s[i]>='0' && s[i]<='9'; ++i)
                n=n*10+(s[i]-'0');
        
            return n;
          }
Al incluir el archivo

        #include <ctype.h>
se pueden utilizar funciones como tolower(c), que convierte un caracter de mayúsculas a minúsculas, isdigit(c), que dice si el carácter es un dígito decimal, isalpha(c), que dice si es un carácter alfabético, etc.

Ejemplo: Implementación de tolower:


        int tolower(int c)
          {
            if(c>='A' && c<='Z')
                c=c-'A'+'a';
        
            return c;
          }

Operaciones con bits

Los enteros se almacenan internamente en binario. Por ejemplo:

(2001)10 = (111 1101 0001)2

         = 210+29+28+27+26+24+20

         = 1024+512+256++128+64+16+1

En C se usa notación octal y hexadecimal. En octal, los bits se agrupan de a 3:

(2001)10 = (11 111 010 001)2 = (3721)8 = 03721
y en hexadecimal se agrupan de a 4:
(2001)10 = (111 1101 0001)2 = (7d1)16 = 0x7d1

Operadores binarios
&and
|or
^xor
<<shift left
>>shift right
~complement

Ejemplos:


        n = n & 0x7f;      /* deja sólo los 7 bits inferiores */
        x = x | MASK;      /* enciende todos los bits en 1 de MASK */
        x = x & ~MASK;     /* apaga todos los bits que están en 1 en MASK */

Ejemplo: Otra implementación para tolower.
Considerando que las el rango A..Z es 0x41..0x5A y que el rango a..z es 0x61..0x7A, entonces podemos escribir:


        int tolower(int c)
          {
            if(c>='A' && c<='Z')
                c |= 0x20;
        
            return c;
          }

Prioridad de Operadores

OperadoresAsociatividad
() [] -> .izquierda
! ~ ++ -- + - * & (tipo) sizeofderecha
* / %izquierda
+ -izquierda
<< >>izquierda
< <= > >=izquierda
== !=izquierda
&izquierda
^izquierda
|izquierda
&&izquierda
||izquierda
?:derecha
= += -= *= /= %= &= ^= |= <<= >>=derecha
,izquierda

Instrucciones de Control

C tiene esencialmente las mismas instrucciones de control que ya hemos visto en Java:


        if
        while
        break
        continue
        ...

Además existe una instrucción goto que se usa con muy poca frecuencia.

La instrucción switch permite seleccionar entre múltiples casos:


        switch( expr )
          {
        case valor1:  /* llega aquí si expr==valor1 */
            ...
            ...
            break;  /* de lo contrario sigue de largo */
        
        case valor2:  /* llega aquí si expr==valor2 */
            ...
            ...
            break;  /* de lo contrario sigue de largo */

            ...
            ...

        default:    /* llega aquí si no es ninguno de los anteriores */
            ...
            ...
          }

La instrucción do es similar al while, pero la comparación se hace al final de cada iteración:


        do
          {
            ...
            ...
          }
        while( expr );

Punteros y Arreglos

La siguiente instrucción asigna a p un puntero a la variable x:


        int x;
        int *p;
        
        p = &x;  /* p apunta a x */

La operación opuesta permite obtener el valor apuntado por p:


        y = *p;  /* Equivalente a y=x */
        
        *p = 0;  /* Equivalente a x=0 */

Esto permite escribir una función que interambia los valores de sus dos parámetros:

intercambia.c


        void intercambia(int *px, int *py)
          {
            int aux;
        
            aux=*px;
            *px=*py;
            *py=aux;
          }
        
        main()
          {
            int a=1, b=2;
        
            printf("ANTES:   a=%d b=%d\n", a, b);
        
            intercambia(&a, &b);  /* NO intercambia(a, b) */
        
            printf("DESPUES: a=%d b=%d\n", a, b);
          }

Relación con arreglos


        int a[10];
        int *p;

a[0] a[1] .... a[9]


        p = &a[0];  /* p apunta al primer elemento de a */
        
        printf("%d\n", *(p+1));  /* imprime a[1] */
        
        printf("%d\n", *(p+i));  /* imprime a[i] */

Además


        p = &a[0];
es equivalente a

        p = a;
de modo que

        *(a+i)  es equivalente a  a[i]

Ejemplo: Cálculo del largo de un string

largo.c


        int my_strlen(char *s)
          {
            int n;
        
            for(n=0; *s!='\0'; ++s)
                ++n;
        
            return n;
          }
        
        main()
          {
            printf("%d\n", my_strlen("hola"));
          }

Implementación alternativa usando aritmética de punteros:

largo2.c


        int my_strlen(char *s)
          {
            char *p=s;
        
            while(*p!='\0')
                ++p;
        
            return p-s;
          }
        
        main()
          {
            printf("%d\n", my_strlen("hola"));
          }

Strcpy con y sin punteros

copias1.c


        /* Versión con arreglos */
        
        #include <stdio.h>
        
        void my_strcpy(char s[], char t[])
          {
            int i;
        
            i=0;
            while((s[i]=t[i])!='\0')
                ++i;
          }
        
        main()
          {
            char a[10];
        
            my_strcpy(a, "hola");
            printf("%s\n", a);
          }

copias2.c


        /* Versión con punteros */
        
        #include <stdio.h>
        
        void my_strcpy(char *s, char *t)
          {
            while((*s=*t)!='\0')
              {
                ++s;
                ++t;
              }
          }
        
        main()
          {
            char a[10];
        
            my_strcpy(a, "hola");
            printf("%s\n", a);
          }

copias3.c


        /* Versión con punteros (más compacta) */
        
        #include <stdio.h>
        
        void my_strcpy(char *s, char *t)
          {
            while((*s++=*t++)!='\0')
                ;
          }
        
        main()
          {
            char a[10];
        
            my_strcpy(a, "hola");
            printf("%s\n", a);
          }

copias4.c


        /* Versión con punteros (más compacta todavía) */
        
        #include <stdio.h>
        
        void my_strcpy(char *s, char *t)
          {
            while(*s++=*t++)
                ;
          }
        
        main()
          {
            char a[10];
        
            my_strcpy(a, "hola");
            printf("%s\n", a);
          }

Memoria Dinámica

La función básica es malloc, que reserva un espacio de memoria del tamaño que se le indique (en bytes) y retorna un puntero. Siempre hay que anteponerle un cast a la llamada, para que el tipo del puntero retornado se adapte a lo que se espera.


        #include <stdlib.h>
        
        char *s;
        
        s = (char *)malloc(100); /* reserva un área de 100 bytes */

Para pedir espacio para un arreglo hay dos alternativas. Una es usar malloc y calcular "a mano" el espacio total requerido. Por ejemplo,


        p = (int *)malloc(100*sizeof(int)); /* p apunta a un arreglo de 100 int's */

La otra forma es usar otra función, calloc, que tiene dos parámetros (número de celdas y tamaño de cada celda):


        p = (int *)calloc(100, sizeof(int)); /* p apunta a un arreglo de 100 int's */

Un efecto lateral de calloc es que inicializa en cero la memoria que entrega.

Al terminar de usar un área de memoria pedida dinámicamente, se la debe retornar al sistema usando


        free(p);

donde p apunta al principio del área que se libera.

En C no hay recolección automática de basura, es responsabilidad del programador liberar la memoria que ya no use. Los programas que funcionan durante mucho tiempo y que van olvidando liberar memoria se dice que tienen un error de tipo "memory leak", el cual puede ser muy difícil de investigar.

Ejemplo:

reves.c


        /* Lee un archivo y lo imprime al revés */
        
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        
        #define LARGOMAX 1000
        #define MAXLINEAS 10000
        
        main()
          {
            char linea[LARGOMAX];
            char *a[MAXLINEAS];  /* arreglo de punteros a las líneas */
            int k;
        
            /* primero leemos las lineas */
        
            for(k=0; fgets(linea, LARGOMAX, stdin)!=NULL; ++k)
              {
                if(k>=MAXLINEAS)
                  {
                    fprintf(stderr, "Archivo demasiado grande\n");
                    exit(-1);
                  }
                a[k]=strdup(linea);  /* Construye una copia del string */
              }
        
            /* ahora las imprimimos al reves */
        
            while(k-->0)
               fputs(a[k], stdout);
          }

La función strdup reserva un espacio de memoria del tamaño exacto para copiar allí el string que recibe como argumento, lo copia y retorna un puntero a esa área. La siguiente función sería equivalente:


        char *my_strdup(char *s)
          {
            char *p;
        
            p=(char *)malloc(strlen(s)+1);
        
            if(p!=NULL)
                strcpy(p,s);
        
            return p;
          }

Argumentos para el programa

El encabezamiento del programa es


        main(int argc, char *argv[])

El arreglo argv tiene argc elementos, los cuales son punteros a los strings que son las palabras de la línea de comandos, comenzando por argv[0], que es el nombre del comando.

Ejemplo:

my_echo.c


        main(int argc, char *argv[])
          {
            int i;
        
            for(i=1; i<argc; ++i)
                printf("%s%c", argv[i], (i<argc-1?' ':'\n'));
          }

Estructuras

Una estructura (o record) agrupa a un conjunto de campos.


        /* Definición de la estructura */
        struct punto
          {
            float x;
            float y;
          };
        
        /* Declaraciones de variables */
        struct punto u, v;
        struct punto z={1.0, -2.5};
        
        /* Acceso a componentes */
        z.x=2.0;
        printf("z=(%f, %f)\n", z.x, z.y);

Punteros a Estructuras


        struct punto *p;
        
        p=&z;
        
        printf("%f\n", (*p).x); /* los paréntesis son necesarios por la prioridad */
        
        
        printf("%f\n", p->x);   /* notación alternativa más simple */

Ejemplo: Creación dinámica de una estructura.


        struct punto *new_punto(float x, float y)
          {
            struct punto *p;
        
            p=(struct punto *)malloc(sizeof(struct punto));
            p->x=x;
            p->y=y;
        
            return p;
          }

Estructuras enlazadas

Ejemplo: Un árbol binario.

arbol.c


        #include <stdio.h>
        #include <string.h>
        
        struct nodo
          {
            char *info;
            struct nodo *izq;
            struct nodo *der;
          };
        
        struct nodo *new_nodo(char *s, struct nodo *i, struct nodo *d)
          {
            struct nodo *p;
        
            p=(struct nodo *)malloc(sizeof(struct nodo));
            p->izq=i;
            p->der=d;
            p->info=strdup(s);
        
            return p;
          }
        
        void preorden(struct nodo *p)
          {
            if(p!=NULL)
              {
                printf("%s\n", p->info);
                preorden(p->izq);
                preorden(p->der);
              }
          }
        
        /* Programa que crea un árbol pequeño y lo recorre en preorden */
        
        main()
          {
            struct nodo *a;
        
            a=new_nodo("A",
                new_nodo("B", NULL, NULL),
                new_nodo("C",
                    new_nodo("D", NULL, NULL),
                    new_nodo("E", NULL, NULL)
                )
              );
        
            preorden(a);
          }

Sintaxis de la declaración de estructura

En C es posible declarar una estructura e inmediatamente declarar variables que poseen esa estructura. Ejemplo:


        struct
          {
            float re;
            float im;
          } z, w;

Si queremos darle un nombre a la estructura, éste va después de la palabra struct:


        struct complex
          {
            float re;
            float im;
          } z, w;
        
        struct complex u, v; /* utiliza la definición anterior */

También es posible no declarar ninguna variable al definir la estructura:


        struct complex
          {
            float re;
            float im;
          };
         
        struct complex z, w, u, v; /* utiliza la definición anterior */

Typedef

La palabra clave typedef se puede anteponer a una declaración de variable y, en lugar de definir una variable, define un nuevo nombre para ese tipo. Ejemplo:


        typedef int ENTERO;
        
        ENTERO x; /* equivalente a int x; */

La principal utilidad de esto es darle nombre a tipos definidos mediante estructuras:


        typedef struct
          {
            float re;
            float im;
          } COMPLEX;
        
        COMPLEX z, w;

No es posible utilizar un nombre definido por typedef dentro de la misma definición. Lo siguiente no funciona:


        typedef struct
          {
            int info;
            NODO *izq;
            NODO *der;
          } NODO;

pero es posible definir el typedef primero y luego el contenido de la estructura:


        typedef struct nodo NODO;
         
        struct nodo
          {
            int info;
            NODO *izq;
            NODO *der;
          };

Uniones

Una union es similar a una estructura, pero los campos están superpuestos en memoria.

Ejemplo:


        union
          {
            int ival;
            float fval;
          } u;

Los campos se accesan igual que en una estructura:


        u.ival
        
        u.fval

y es responsabilidad del programador asegurarse de que el campo tiene un valor del tipo correcto. Para esto a menudo se utiliza un campo adicional (fuera de la unión) llamado un selector.

Ejemplo:

union.c


        #include <stdio.h>
        
        struct numero
          {
            int tipo; /* selector */
            union
              {
                int ival;
                float fval;
              } u;
          };
        
        enum{INT, FLOAT}; /* Equivale a #define INT 0, #define FLOAT 1 */
        
        void imprimir(struct numero a)
          {
            switch(a.tipo)
              {
            case INT:
                printf("%d\n", a.u.ival);
                break;
        
            case FLOAT:
                printf("%f\n", a.u.fval);
              }
          }
        
        main()
          {
            struct numero x;
        
            x.tipo=INT;
            x.u.ival=5;
            imprimir(x);
        
            x.tipo=FLOAT;
            x.u.fval=3.14;
            imprimir(x);
          }

Entrada/Salida

Ya hemos visto las funciones básicas de entrada/salida de a un caracter a la vez:


        c=getchar();
        
        putchar(c);

Para imprimir con formato:


        printf(formato, arg1, ...);

Códigos de formato

%d, %idecimal
%ooctal
%x, %Xhexadecimal
%uunsigned
%ccaracter
%sstring
%fflotante
%e, %Eflotante con exponente
%g, %Gflotante (general)
%%%

La función printf retorna el número de caracteres que ha escrito.

La función sprintf es similar, pero escribe hacia un string, no hacia la salida:


        n = sprintf(s, formato, arg1, ...);

La función opuesta es


        n = scanf(formato, &arg1, ...);

Nótese que los parámetros son las direcciones de la variables cuyos valores se leen bajo el control del formato.

También existe la función sscanf.

Acceso a Archivos


        #include <stdio.h>

        FILE *fp;  /* file pointer */

        fp=fopen(nombre, modo)
         /* modo = "r", "w", "a" */
         /* fopen retorna NULL en caso de error */

        fclose(fp);

        c=getc(fp);
        putc(c, fp);

        ungetc(c, fp);

        #define getchar()   getc(stdin)
        #define putchar(c)  putc((c), stdout)

        fprintf(fp, formato, ...);
        fscanf(fp, formato, ...);

Ejemplo: Comando cat

my_cat.c


        #include <stdio.h>
        
        void filecopy(FILE *in, FILE *out)
          {
            int c;
        
            while((c=getc(in))!=EOF)
                putc(c, out);
          }
        
        main(int argc, char *argv[])
          {
            FILE *fp;
        
            if(argc==1) /* sin argumentos */
                filecopy(stdin, stdout);
            else
                while(--argc>0)
                    if((fp=fopen(*++argv, "r"))==NULL)
                      {
                        fprintf(stderr, "No se puede abrir archivo '%s'\n", *argv);
                        exit(1);
                      }
                    else
                      {
                        filecopy(fp, stdout);
                        fclose(fp);
                      }
          }

setjmp/longjmp

Estas funciones permiten salirse desde el fondo de un conjunto de llamadas a funciones:


        #include <setjmp.h>
        
        jmp_buf env;
        
        if(setjmp(env)==0)
          {
            /* Aquí se ejecuta el proceso, el cual puede contener llamadas
               a funciones de cualquier nivel de profundida. En cualquier
               momento se puede ejecutar
        
                       longjmp(env, val);
        
               Esto concluye abruptamente la ejecución y se retorna a la
               otra rama de este if
            */
          }
        else
          {
            /* Aquí se llega después de que se ejecuta un longjmp en la otra
               rama del if. El segundo parámetro del longjmp se recibe como
               el valor retornado por el setjmp, y debe ser distinto de cero
            */
          }

Punteros a funciones

No existen variables de tipo "función", pero es posible tener una variable que es un puntero a una función. Esto es especialmente útil como parámetros para funciones.

qsort.c


        /* ordena datos de cualquier tipo usando Quicksort */
        
        void swap(void *v[], int i, int j)
          {
            void *aux;
        
            aux=v[i];
            v[i]=v[j];
            v[j]=aux;
          }
        
        void qsort(void *a[], int left, int right,
                   int (*compare)(void *, void *))
          {
            int i, last;
        
            if(left>=right)
                return;
        
            swap(a, left, (left+right)/2);
            last=left;
        
            /*
                  +--+-----------+--------+--------------+
                  |  |///////////|\\\\\\\\|              |
                  +--+-----------+--------+--------------+
                  left        last         i         right
            */
        
            for(i=left+1; i<=right; ++i)
                if((*compare)(a[i], a[left])<0)
                    swap(a, ++last, i);
            swap(a, left, last);
        
            qsort(a, left, last-1, compare);
            qsort(a, last+1, right, compare);
          }

El siguiente programa utiliza la función anterior para ordenar líneas lexicográficamente:

ordena1.c


        #include <stdio.h>
        #include <string.h>
        
        #define ANCHO 1000
        #define MAX 10000
        
        main()
          {
            char s[ANCHO];
            char *linea[MAX];
            int i, j;
            void qsort(void **, int, int, int (*)(void *, void *));
        
            for(i=0; fgets(s, ANCHO, stdin)!=NULL; ++i)
                linea[i]=strdup(s);
        
            qsort((void **)linea, 0, i-1, strcmp);
        
            for(j=0; j<i; ++j)
                fputs(linea[j], stdout);
          }

Podemos hacer más general este programa si agregamos una opción -n para pedir que la ordenación sea en forma numérica:

ordena2.c


        #include <stdio.h>
        #include <string.h>
         
        #define ANCHO 1000
        #define MAX 10000
        
        int numcmp(char *s1, char *s2) /* compara numéricamente */
          {
            int i1, i2;
        
            i1=atoi(s1);
            i2=atoi(s2);
        
            return(i1<i2? -1 : i1==i2? 0 : 1);
          }
         
        main(int argc, char *argv[])
          {
            char s[ANCHO];
            char *linea[MAX];
            int i, j, numeric=0;
            void qsort(void **, int, int, int (*)(void *, void *));
        
            numeric=(argc==2 && strcmp(argv[1], "-n")==0);
            if(argc>2|| argc==2 && !numeric)
              {
                fprintf(stderr, "Use: ordena [-n]\n");
                exit(1);
              }
         
            for(i=0; fgets(s, ANCHO, stdin)!=NULL; ++i)
                linea[i]=strdup(s);
         
            qsort((void **)linea, 0, i-1, (numeric?numcmp:strcmp));
         
            for(j=0; j<i; ++j)
                fputs(linea[j], stdout);
          }

Funciones con un número variable de parámetros

El siguiente ejemplo muestra como se puede escribir una función con un número variable de parámetros, al estilo de printf:

printf.c


        #include <stdio.h>
        #include <stdarg.h>
        
        void miniprintf(char *fmt, ...)
          {
            va_list ap; /* argument pointer */
            char *p, *sval;
            int ival;
            double dval;
        
            va_start(ap, fmt); /* fmt es el último parámetro de la parte no variable */
            for(p=fmt; *p; ++p)
              {
                if(*p!='%')
                  {
                   putchar(*p);
                   continue;
                  }
        
                switch(*++p)
                  {
                case 'd':
                    ival=va_arg(ap, int);
                    printf("%d", ival);
                    break;
        
                case 'f':
                    dval=va_arg(ap, double);
                    printf("%f", dval);
                    break;
        
                case 's':
                    sval=va_arg(ap, char *);
                    printf("%s", sval);
                    break;
        
                default:
                    putchar(*p);
                    break;
                  }
              }
            va_end(ap);
          }
        
        main()
          {
            miniprintf("%s %d %f\n", "hola", 5, 3.14);
          }

Traspaso de parámetros a funciones del tipo printf

Las funciones vfprintf, vprintf y vsprintf son similares a sus homólogas sin "v", pero reciben un puntero ap en lugar de una lista variable. Esto permite escribir una función similar a printf que internamente llame a esta última.

subraya.c


        /* Imprime subrayando */
        
        #include <stdio.h>
        #include <stdarg.h>
        
        void ulprintf(char *fmt, ...)
          {
            int n;
            va_list ap;
        
            va_start(ap, fmt);
            n=vprintf(fmt, ap);
            va_end(ap);
        
            while(--n>0)
                putchar('=');
            putchar('\n');
          }
        
        main()
          {
            int a=5;
            float b=4.2;
        
            ulprintf("a=%d b=%3.1f\n", a, b);
            ulprintf("fin\n");
          }

La Biblioteca Standard de Entrada/Salida

A continuación veremos un resumen de las funciones disponibles: