Formatted Input and Output in C

In this lesson we will study the library functions of the C language that allow reading and writing files and that use the so-called format strings.

Such functions, which to some extent also include the standard input and output functions printf() and scanf(), allow reading and writing structured data by specifying the format with which the data must be interpreted or written, using precisely format strings.

They work by converting numeric data into textual data (in the case of writing) or converting textual data into numeric data (in the case of reading), according to the specifications indicated in the format string. The other input and output functions that we will see in the next lessons do not have, instead, such capability.

Key Takeaways
  • The fprintf and fscanf functions allow writing and reading formatted data on files using format strings.
  • The fprintf function writes data on a file according to the specified format.
  • The fscanf function reads data from a file according to the specified format.
  • Both functions accept a variable number of arguments and return the number of elements written or read successfully.
  • Format strings use format specifiers to represent different data types, such as integers, floating-point numbers, characters and strings.

The fprintf function

The fprintf() function is very similar to the printf() function, but instead of writing the output to the screen, it writes it to a file.

Both functions take as input a format string to control how the data must be written. The signature of the two functions is as follows:

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

We notice a first thing: both functions present a list of parameters that ends with three dots ..., which indicates that they accept a variable number of arguments.

Furthermore, the value returned by them is an integer that represents the number of characters written (excluding the null termination character \0), or a negative value in case of error.

The main difference between the two functions is that fprintf() requires as first parameter a pointer to an object of type FILE, which represents the file on which to write the output, while printf() does not require such parameter because it writes directly to the standard output (the screen).

Actually, the standard output is represented by an object of type FILE called stdout, which is defined in the standard library of the C language. Therefore, we can consider that the call to printf() is equivalent to:

// Equivalent calls
printf("Hello, World!\n");
fprintf(stdout, "Hello, World!\n");

The two calls above produce the same result, writing the string "Hello, World!" followed by a newline character to the screen.

The flexibility of the fprintf() function lies in the fact that it is not a simple file writing function. It works, in fact, with any output stream, which can be a file, the standard output, or even a standard error stream (stderr). In fact, the latter is its most common use when one wishes to write error messages.

For example, to write an error message to the standard error, we can use fprintf() in this way:

fprintf(stderr, "Error: Unable to open the file.\n");

Writing an error message to the standard error is useful because it allows separating error messages from the normal output of the program. By doing so, if the user has redirected the program output to a file, the error messages will continue to be displayed on the screen, making it easier to identify and resolve any problems.

Both printf() and fprintf() support a wide range of format specifiers to represent different data types, such as integers, floating-point numbers, characters and strings. We have already examined some of them in previous lessons: for example we have seen how to write integers with printf and write floating-point numbers with printf. The same format specifiers can be used with fprintf().

In any case, we will examine in detail the various format specifiers in the next lessons.

Since the functioning of fprintf() is very similar to that of printf(), we speak of printf family of functions, which also includes other more or less obscure functions such as the vprintf() and vfprintf() function which however we will study later.

Example of using fprintf

Now, let's see an example of using the fprintf() function to write data to a file. Suppose we want to create a text file called data.txt and write some formatted information inside it.

For example, we want to write a program that saves the square and cube of the first 20 positive integers in a text file. We want the structure of the file to be as follows:

    Number     Square       Cube
--------------------------------
         1          1          1
         2          4          8
         3          9         27
...
        20        400       8000

Therefore, the numbers must be right-aligned in columns of fixed width of 10 characters. Furthermore, the file must have a header that describes the content of the columns.

Let's start to realize the skeleton of the program:

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

int main() {
    FILE *file;
    int i;

    // Opens the file in write mode
    file = fopen("data.txt", "w");
    if (file == NULL) {
        printf("Error in opening the file\n");
        return EXIT_FAILURE;
    }

    // Writes the file header
    // ...

    // Writes the data in the file
    for (i = 1; i <= 20; i++) {
        // Calculates the square and the cube
        int square = i * i;
        int cube = square * i;

        // Writes the formatted data in the file
        // ...
    }

    // Closes the file
    fclose(file);

    return EXIT_SUCCESS;
}

This skeleton contains the main parts of the program. The parts where we write the header and the formatted data in the file are still missing.

To write the file header, we can specify a format string that aligns the column titles. We use the fprintf() function to write the header:

// Writes the file header
int header_length =
    fprintf(file, "%10s %10s %10s\n", "Number", "Square", "Cube");

In this line, we use the format specifier %10s to right-align the strings "Number", "Square" and "Cube" in columns of width 10 characters.

Furthermore, we store the length of the header to be able to write a separation line of the same length. In fact, fprintf() returns the number of characters written, which we use to create the separation line:

// Writes the separation line
for (i = 0; i < header_length - 1; i++) {
    fprintf(file, "-");
}
fprintf(file, "\n");

Now, to write the formatted data in the file, we use another format specifier to align the integers. We use %10d to right-align the numbers in columns of width 10 characters:

// Writes the data in the file
for (i = 1; i <= 20; i++) {
    // Calculates the square and the cube
    int square = i * i;
    int cube = square * i;
    // Writes the formatted data in the file
    fprintf(file, "%10d %10d %10d\n", i, square, cube);
}

Combining all the parts, we obtain the following complete program:

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

int main() {
    FILE *file;
    int i;

    // Opens the file in write mode
    file = fopen("data.txt", "w");
    if (file == NULL) {
        printf("Error in opening the file\n");
        return EXIT_FAILURE;
    }

    // Writes the file header
    int header_length =
            fprintf(file, "%10s %10s %10s\n", "Number", "Square", "Cube");

    // Writes the separation line
    for (i = 0; i < header_length - 1; i++) {
        fprintf(file, "-");
    }
    fprintf(file, "\n");

    // Writes the data in the file
    for (i = 1; i <= 20; i++) {
        // Calculates the square and the cube
        int square = i * i;
        int cube = square * i;
        // Writes the formatted data in the file
        fprintf(file, "%10d %10d %10d\n", i, square, cube);
    }

    // Closes the file
    fclose(file);

    return EXIT_SUCCESS;
}

If we compile and execute this program, we will obtain a text file called data.txt with the following content:

    Number     Square       Cube
--------------------------------
         1          1          1
         2          4          8
         3          9         27
         4         16         64
         5         25        125
         6         36        216
         7         49        343
         8         64        512
         9         81        729
        10        100       1000
        11        121       1331
        12        144       1728
        13        169       2197
        14        196       2744
        15        225       3375
        16        256       4096
        17        289       4913
        18        324       5832
        19        361       6859
        20        400       8000

The fscanf function

Analogously to fprintf(), the fscanf() function is similar to the scanf() function, but instead of reading the input from the keyboard, it reads it from a file.

Also fscanf() uses a format string to specify how the data must be read and converted. The signature of the two functions is as follows:

int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);

Like fprintf(), fscanf() requires as first parameter a pointer to an object of type FILE, which represents the file from which to read the input, while scanf() does not require such parameter because it reads directly from the standard input (the keyboard).

Furthermore, also in this case, both functions accept a variable number of arguments and return an integer that represents the number of elements read successfully, or EOF in case of error or end of file.

After the parameter that specifies the format string, both functions require a series of pointers to the variables in which to store the read data.

For example, if we want to read an integer and a floating-point number from the keyboard, we can use scanf() in this way:

int number;
float real;
scanf("%d %f", &number, &real);

Such call is equivalent to:

int number;
float real;
fscanf(stdin, "%d %f", &number, &real);

The two functions, fscanf and scanf, return prematurely if one of the following events occurs:

  1. The end of file (EOF) is reached. In the case of scanf(), this can happen if the user sends an EOF signal (for example, pressing Ctrl+D on Unix or Ctrl+Z followed by Enter on Windows).
  2. A matching error occurs between the input and the format string. For example, if one attempts to read an integer but the provided input is not a valid number, the function will stop and return the number of elements read successfully up to that point.

Therefore the return value of fscanf() and scanf() can be used to verify if the reading was successful and how many elements were read correctly. The value EOF indicates that the end of file was reached or no element was read due to an error.

In C programs, while loops are often used that continue to read data from a file until the end of file is reached and that test the return value to verify if the reading was successful.

For example, wanting to read a series of integers from a text file until the end of file, we can use the following program scheme:

while (fscanf(file, "%d", &number) == 1) {
    // Processes the read number
}

Example of using fscanf

Let's see an example of using the fscanf() function to read data from a file.

Suppose we have a text file called data.csv that contains a series of integers in CSV (Comma-Separated Values) format, as shown below:

10,20,30
40,50,60
70,80,90

A CSV file is a common format for storing tabular data, in which the values are separated by commas and each row represents a record. It can be easily created and read by spreadsheet programs such as Microsoft Excel or Google Sheets.

Suppose our data.csv file contains a series of integers arranged in triplets on each row, separated by commas. We want to read these numbers and calculate the sum of all the columns of the file.

Let's start to realize the skeleton of the program:

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

int main() {
    FILE *file;
    int num1, num2, num3;
    int sum1 = 0, sum2 = 0, sum3 = 0;

    // Opens the file in read mode
    file = fopen("data.csv", "r");
    if (file == NULL) {
        printf("Error in opening the file\n");
        return EXIT_FAILURE;
    }

    // Reads the data from the file
    while (/* Reading condition */) {
        // Processes the read numbers
        // ...
    }

    // Prints the sums of the columns
    printf("Sum column 1: %d\n", sum1);
    printf("Sum column 2: %d\n", sum2);
    printf("Sum column 3: %d\n", sum3);

    // Closes the file
    fclose(file);

    return EXIT_SUCCESS;
}

To read the data from the file, we use the fscanf() function inside a while loop. The reading condition must verify if three integers separated by commas were read correctly. The format string for fscanf() will therefore be "%d,%d,%d".

// Reads the data from the file
while (fscanf(file, "%d,%d,%d", &num1, &num2, &num3) == 3) {
    // Processes the read numbers
    sum1 += num1;
    sum2 += num2;
    sum3 += num3;
}

In this way, the loop will continue to read triplets of numbers until the end of file is reached or a reading error occurs.

Combining all the parts, we obtain the following complete program:

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

int main() {
    FILE *file;
    int num1, num2, num3;
    int sum1 = 0, sum2 = 0, sum3 = 0;

    // Opens the file in read mode
    file = fopen("data.csv", "r");
    if (file == NULL) {
        printf("Error in opening the file\n");
        return EXIT_FAILURE;
    }

    // Reads the data from the file
    while (fscanf(file, "%d,%d,%d", &num1, &num2, &num3) == 3) {
        // Processes the read numbers
        sum1 += num1;
        sum2 += num2;
        sum3 += num3;
    }

    // Prints the sums of the columns
    printf("Sum column 1: %d\n", sum1);
    printf("Sum column 2: %d\n", sum2);
    printf("Sum column 3: %d\n", sum3);

    // Closes the file
    fclose(file);

    return EXIT_SUCCESS;
}

If we compile and execute this program with the data.csv file shown above, we will obtain the following output:

Sum column 1: 120
Sum column 2: 150
Sum column 3: 180