Skip to main content

Section 4.14 Input and Output

The header file stdio.h of the C Standard Library provides elementary functions for input and output. A file is here represented by a pointer to a FILE. The struct FILE is opaque (i.e. encapsulated away from us), we therefore do not know its internal structure and only use pointers to it.

stdio.h defines three global variables:

FILE *stdin;
FILE *stdout;
FILE *stderr;

They are called standard input, standard output, and error output.

Files can be opened with

FILE* fopen(char const *filename, char const *mode);

and closed again with

void fclose(FILE*);

fopen requires a file name and a mode, which specifies how the file is opened ("r" for reading, "w" for writing). It produces a pointer to a FILE if the file was successfully opened or NULL otherwise. For reading and writing, we often use the functions

int fscanf(FILE* f, char const* format, ...);
int fprintf(FILE* f, char const* format, ...);

The commonly used functions printf and scanf are abbreviations for these where f is set to stdin or stdout, respectively.

Subsection 4.14.1 Format Strings

Both, fprintf and fscanf use so-called format strings to determine how input and output is formatted. The chosen format element also describes how the corresponding argument is to be interpreted. In the following, we give a few examples of format strings and refer to the corresponding Unix manpages for a complete documentation.

char const *weekday = "Wednesday";
char const *month = "March";
int day = 11;
int hour = 10;
int min = 30;
printf("%s, %s %d, %.2d:%.2d\n",
    weekday, month, day, hour, min);

produces this output:

Wednesday, March 11, 10:30

Floating point numbers can be printed as follows:

printf("pi = %.5f\n", 4 * atan(1.0));

resulting in:

pi = 3.14159

Example 4.14.1. Running Average.

We want to write a program that computes the average of all numbers from a list of files. A technique to compute the average of a list of numbers is the running average. If we know the average \(s_n\) of the first \(n\) numbers \(a_1, \dots, a_n\text{,}\) the average of the numbers \(a_1, \dots, a_{n+1}\) can be computed as:

\begin{equation} s_{n+1} = \frac n{n+1} s_n + \frac 1{n+1} a_{n+1}\tag{4.1} \end{equation}

We start with a function that computes the average of all numbers given in one file. We assume that the input files contain textual representations of floating-point numbers that are separated by whitespace characters as in this example:

1 2.2 3
4.2
1.4

We convert the textual representation to a double floating-point value using fscanf and store it into a variable val. The average and the number of the read numbers are stored in a struct:

typedef struct {
    double avg;
    unsigned n;
} avg_t;

The function running_average expects a file and a pointer to such a struct as arguments. It then reads as long as it can find numbers in the file and continues the average computation according to (4.1):

avg_t *running_average(FILE* f, avg_t* avg)
{
    double val;
    while (fscanf(f, "%lg", &val) == 1) {
        double   old_n = avg->n;
        unsigned new_n = ++avg->n;
        avg->avg = (old_n / new_n) * avg->avg + val / new_n;
    }
    return avg;
}

The following main function accepts a list of file names as command line parameters. It opens these files one after the other and calls the running_average function with it. Lastly, it prints the computed average. If no command line parameter is given, the standard input stdin is used as an input file.

int main(int argc, char *argv[])
{
    avg_t avg;
    avg.avg = 0.0;
    avg.n   = 0;

    if (argc == 1) {
        running_average(stdin, &avg);
    }
    else {
        for (int i = 1; i < argc; i++) {
            FILE* input;
            if ((input = fopen(argv[i], "r")) != NULL) {
                running_average(input, &avg);
                fclose(input);
            }
            else {
                fprintf(stderr, "no such file: %s\n", argv[i]);
                return 1;
            }
        }
    }
    printf("%g\n", avg.avg);
    return 0;
}