Last Updated:

Overview of I/O Tools in C++

An application written in any programming language should interact with the outside world. Otherwise, there will be no benefit from it. As a rule, such interaction is carried out by input-output of information to the monitor or to a file. True, there are a number of programs that do not use file or console input-output: these are programs that carry out low-level interaction with the hardware of the computer and peripherals (OS kernel, drivers, etc.), but this is already exotic.

In standard C++, there are two main ways of I/O information: using threads implemented in the STL (Standard Template Library) and through the traditional I/O system inherited from C. If you dig a little deeper, you will find that both threads and the traditional I/O system use operating system calls to perform the necessary actions. And rightly so.

The following exposition does not pretend to be complete, but describes the basic principles of using libraries. Details of the use can be found in the numerous literature on C++ and STL, in MSDN, etc.

Traditional I/O

 

To use a traditional C-style I/O, you must include a header file in the program. (Of course, the compiler must have access to the appropriate object library to properly assemble the executable.)<cstdio>

The stdio library provides the necessary set of functions for input and output of information in both text and binary representations. It should be noted that unlike the classic C library, modern libraries have more secure analogues of "classical" functions. As a rule, they have the same name to which the suffix _s is added. It is recommended to use these safe functions.

The very first program using the stdio library looks like this:

#include <cstdio>

int main()
{
    printf("Hello, world!\n");
}

When you run a console application, three threads are implicitly opened: stdin for keyboard input, stdout for buffered monitor output, and stderr for unbuffered error messages. These three characters are defined by .<cstdio>

Stdio provides a separate group of functions for console I/O. However, these functions are typically wrappers for similar file I/O functions for which the FILE type argument is set by default.

The very first program using file output from the stdio library looks like this:

#include <cstdio>

int main()
{
    fprintf(stdout, "Hello, world!\n");
}

Some popular features from stdio are:

FILE *fopen(const char *filename, const char *mode) // open file
int fclose(FILE *stream) // close file

int printf(const char *format, ...) // formatted console output
int fprintf(FILE *stream, const char *formatb, ...) // formatted input from file
int sprintf(char *s, const char *format, ...) // formatted output to buffer (string)

int scanf(const char *format, ...) // formatted console input
int fscanf(FILE *stream, const char *format, ...) // formatted input
int sscanf (const char *s, const char *format, ...) // formatted input from buffer (string)

int fgetc(FILE *stream) // reads a character from a file
char *fgets(char *s, int n, FILE *stream) // reads a line from a file
int fputc(int c, FILE *stream) // writes a character to a file
int fputs(const char *s, FILE *stream) // writes a string to a file
int getchar(void) // reads a character from stdin
char *gets(char *s) // reads a line from stdin
int putchar(int c) // writes a character to stdout
int puts(const char *s) // writes a string to stdout
int ungetc(int c, FILE *stream) // returns a character back to the file for later reading

FILE entity is a structure that stores all the information to control the flow of I/O.

The file is opened by the fopen() function, to which two parameters are passed. The first parameter specifies the name of the file. The second one defines the mode of opening the file: reading, writing, random access, etc., as well as specifying how to work with data: in text or binary mode. See the documentation for details.

Example of using stdio

#include <cstdio>
#include <errno.h>

const char *filename = "testfile.txt";

int main()
{
    FILE *fin, *fout;
    int code;

    // open file for writing in text mode,
    // writing data and closing the file.
    if((fout = fopen(filename, "w")) != NULL) {
        for (int i = 0; i < 16; i++) {
            if ((ecode = fprintf(fout, "%d\n", i*i)) <= 0) {
                fprintf(stderr, "Write error in file \"%s\", code %d\n", filename, ecode);
                fclose(fout);
                return 1;
            }
        }
        fclose(fout);
    }
    else {
        fprintf(stderr, "Output file open error \"%s\", code %d\n", filename, errno);
        return 1;
    }

    // open file for reading in text mode,
    // reading data, formatted output to the console, closing the file.
    intdata;
    int counter = 0;
    if((fin = fopen(filename, "r")) != NULL) {
        while ((ecode = fscanf(fin, "%d", &data)) != EOF) {
            printf("%8d", data);
            if (++counter % 4 == 0) {
                putchar('\n');
            }
        }
        if ((ecode = ferror(fin)) != 0) {
            fprintf(stderr, "Read error in file \"%s\", code %d\n", filename, ecode);
            fclose(fin);
            return 2;
        }
        fclose(fin);
    }
    else {
        fprintf(stderr, "Input file open error \"%s\", code %d\n", filename, errno);
        return 2;
    }
    return 0;
}

It should be noted that there is another library focused exclusively on console I/O - .<conio.h>

I/O using STL streams

To use object-oriented console I/O using STL streams, you must include a header file in the program, and for file-based, you must also include . (Of course, the compiler must have access to the appropriate object library to properly assemble the executable.)<iostream><fstream>

The very first program using STL streams looks like this:

#include <iostream>

using namespace std;

int main() {
    cout << "Hello, world!\n";
}

When you run a console application, four threads are implicitly opened: cin for keyboard input, 9 for buffered output to the monitor, cerr for unbuffered error message output, and clog for buffered cerr. These four characters are defined by .<iostream>

The cincout, and cerr threads correspond to the stdinstdout, and stderr threads, respectively.

The hierarchy of STL I/O classes is quite complex. Lovers of subtle sensations can find its description in literature. However, the rest will also not pass the cup, but only later, when knowledge slightly higher than the basic level described here is required.

For I/O, you must first create a stream—an instance of the appropriate STL class—and then associate it with the file. The output stream uses the ofstream class, the input stream uses ifstream, and the I/O stream uses fstream. Each of these classes has an open() method that associates the stream with the file. Simply put, opens the file. Two parameters are passed to the method: the file name and the file open mode. The second parameter is a set of bit flags that determine the mode of opening the file (reading, writing, etc.) and the way to work with data (text or binary mode). The second parameter is optional, i.e. has a default value corresponding to the class.

ifstream::open(const char *filename, ios::openmode mode = ios::in);
ofstream::open(const char *filename, ios::openmode mode = ios::out | ios::trunc);
fstream::open(const char *filename, ios::openmode mode = ios::in | ios::out);

The above classes also have constructors that allow you to open the file as soon as you create a stream. The parameters of these constructors are exactly the same as the parameters of the open() method.

If a file open fails (in the context of a Boolean expression), the thread is set to false.

The file is closed with the close() method. This method is also called when instances of thread classes are destroyed.

Reads and writes to the stream associated with the file are performed either by using the << and >> statements that are overloaded for the I/O stream classes, or by using any other methods of the I/O stream classes.

Some of the most commonly used methods are:

// read data:
getline() // reads a line from the input stream.
get() // reads a character from the input stream.
ignore() // skips the specified number of elements from the current read position.
read() // reads the specified number of characters from the input stream and stores them in a buffer (raw input).

// write data:
flush() // output the contents of the buffer to a file (with buffered I/O)
put() // outputs a character to the stream.
write() // prints the specified number of characters from the buffer to the stream (unformatted output)

Example of using STL streams

#include <iostream>
#include <fstream>

using namespace std;

const char *filename = "testfile2.txt";


int main() {

    // creating a stream, opening a file for writing in text mode,
    // writing data and closing the file.
    outstream ostr;
    ostr open(filename);
    if (ostr) {
        for (int i = 0; i < 16; i++) {
            ostr << i*i << endl;
            if (ostr.bad()) {
                cerr << "Unrecoverable write error" << endl;
                return 1;
            }
        }
        ostr.close();
    }
    else {
        cerr << "Output file open error \"" << filename << "\"" << endl;
        return 1;
    }

    // opening a file (in the constructor) for reading in text mode,
    // reading data, formatted output to the console, closing the file.
    intdata;
    int counter = 0;
    ifstream istr(filename);
    if (istr) {
        while (!(istr >> data).eof()) {
            if (istr.bad()) {
                cerr << "Unrecoverable read error" << endl;
                return 2;
            }
            cout.width(8);
            cout << data;
            if (++counter % 4 == 0) {
                cout << endl;
            }
        }
        istr.close();
    }
    else {
        cerr << "Input file open error \"" << filename << "\"" << endl;
        return 2;
    }

    return 0;
}

Interaction between streaming and traditional I/O

C++ apologists recommend using only STL threads for I/O and abandoning the use of traditional I/O in the spirit of C. However, there is nothing to prevent, at least for now, the use of a traditional I/O system. Moreover, there is a special function for synchronizing I/O performed through threads and through old functions.

#include <iostream>
bool sync_with_stdio(bool sync = true);

Conclusion

Which mechanism to use is a matter of preference for the programmer, unless the employer explicitly prescribes the use of a specific mechanism. In either case, the operating system calls are used for physical I/O. Everything else is a wrapper, a set of more or less convenient functions or classes for interacting with the OS.

Using the threading mechanism is considered safer. But, as you know, a bad program can be written in any programming language. This also applies to the use of libraries.