The C++ Programming Language

Part 10: Input / Output

The input/output part of the C++ Standard Library is bug and important that it requires a dedicated chapter. The only input and output we have seen so far is std::cin and std::cout used to read some basic input from the keyboard and writing some simple text to the screen. Now we are going to go through the input/output provided by the C++ Standard Library.

std::cin & std::cout

In C++ we call the I/O mechanism stream because of the stream of information between our program and the I/O. So we use stream classes for input and output operations between our program and files or devices.

So far we have seen std::cin and std::cout that read the keyboard and print on the screen, respectively. They are predefined objects in the library:

void basic_io_s()
{
    std::string s;
    std::cout << "please type your name:";
    std::cin >> s;
    std::cout << "Hello " << s << "\n";
}

The stream operator (<<) lets us concatenate one object after the other.

std::stringstream

std::stringstream class brings together strings and streams. It works both for input and for output. We can output to this stream like we do with std::cout, and then get the string representation of our output, or read a string as if we are reading from the std::cin.

void strinstream_s()
{
    // initialize the stream with a string
    std::stringstream s("this is a string with several words");
    std::string w;
    std::vector<std::string> v;

    // while we are reading words
    while (s >> w) {
        // and put them in a vector
        v.push_back(w);
    }
    // this will be an output stream
    std::stringstream t;
    // output the words followed by a hyphen
    std::for_each(v.begin(), v.end(), [&t, &w](std::string& s) {std::cout << s << " "; t << s << "-"; });
    // display the result
    std::cout << "\n" << t.str();
}

File streams

File streams are used to perform input and output operations on files. Files are used for persistent storage. Here we are going to see what is in the C++ Standard Library that we can use to read and write to files.

Files

By definition computer files are resources used to store information either to share or for later retrieval. Files are stored in non-volatile computer resources like hard disks, solid state disks and other ultra-fast and highly dense media.

We use files to store images, text, the state of our program and any kind of data we generate working with our computers.

File types

Files are divided in two main categories. Text files and binary files.

Text files are used for text storage, or human readable representation of the information. Binary files contain raw memory contents only the computer can interpret and use.

This organization of information within a file is what we call file format.

File operations

These are some of the operations we usually do with files

Using files

In order to use files in our program we need the <fstream> library, which is part of the C++ Standard Library. This library defines the three new data types we need to work with files.

ofstreamThis is the output file stream. It is used to create files and write information.
ifstreamThis is the input file stream. It is used to read information from existing files.
fstreamThis is a general file stream. It has the capabilities of both ofstream and ifstream and can be used for any operation.

Open a file

The first thing we must do to use a file is to open it. To open a file for writing we need either ifstrem or fstream, whereas to open it for reading we need ofstream or fstream.

The file can is opened using the open function:

std::fstream o;
o.open("some file.txt", std::ios::mode);

The arguments are the file name and the mode of the file.

ios::appOpen the file for appending data
ios::ateOpen the file for appending data and move rea/write control to the end
ios::inOpen the file for input
ios::outOpen the file for output
ios::truncOpen the file and if it exists truncate its contents
Ios::binaryOpen the file in binary mode, default is text mode

Closing a file

When we are done with the file it is a good practice to close it. The library takes care of this automatically when the stream object goes out of scope but depending on the complexity of our code this might take some time, so it is recommended that we do it ourselves.

o.close();

Writing text to a file

The fact that we have a stream makes this operation quite easy. We have seen how to write to the standard output stream. Writing to a file stream is the same. Just replace the stream object with our file stream object.

void filewrite_s()
{
    std::ofstream o;
    o.open("some file.txt", std::ios::out);

    o << "some text";
    o.close();

    o.open("some file.txt", std::ios::app);
    for (int i=0;i<10;i++)
        o << "some more text\n";
    o.close();
}

Reading text from a file

Reading text from a file is like reading the keyboard. We can use the stream operator and we will read the file word by word. The input stream breaks at white space. We can overcome this if we use the getline function which reads to the end of the line.

void fileread_s()
{
    std::ifstream is;
    is.open("some file.txt", std::ios::in);

    int count = 0;
    std::string s;
    // using stream operator reads word by word
    // while (is >> s)
    //  this reads line by line
    while (getline(is, s))
    {
        std::cout << s << "\n";
        // depending on the loop we count lines or words
        ++count;
    }
    is.close();
    std::cout << "count:" << count << "\n";
}

Stream operators output text and text representation of numbers. Human readable information is the default in the library. Whatever we see on the screen goes into the file. This means that we will have to use other functionality within the library to write to a binary file.

Binary vs text

The basic component of the way information is stored in the computer is the binary digit or bit. A bit can have two values, either 1 or 0. Eight bits form one byte. Bytes can store a number from -128 to 127 or 0 to 255 depending on if we want signed or unsigned numbers.

By convention we use an unsigned byte to represent text. The value 48 is for 0, 49 for 1, 65 for A, 66 for B and so on. This is called the ASCII table of characters.

When we save ‘1’ in a text file, we write the number 49, whereas in a binary file we just write 1. In all other operations we use the value 1 and not 49 when we talk about 1.

This is the basic difference between text and binary data and files for that matter. A convention we have to live with and as software developers we have to deal with.

Binary output

Now that we have cleared what we want to do is the time to see how we can do it.

The first solution is the function write, which is member of the stream object. Contrary to the stream operator that coverts from binary to text, this function outputs raw data from the computer memory to the file. It can be used to output any primitive type such as int, double, char and the rest.

void write_binary_s()
{
    std::ofstream o;
    // open the file for binary output
    o.open("some file.dat", std::ios::out | std::ios::binary);
    // write an integer
    int i = 1;
    o.write((char*)&i, sizeof(int));
    //write a character
    char c = '3';
    o.write((char*)&c, sizeof(char));
    // save a text string
    std::string s("some text");
    // first save its length
    i = (int)s.length();
    o.write((char*)&i, sizeof(int));
    // and then the contents
    o.write((char*)s.c_str(), i);
    o.close();
}

Reading binary files

When reading from binary files we have to rely on functions instead of stream operators again. TThe function to use for reading is read. Here we read the file we created before:

void read_binary_s()
{
    std::ifstream ifs;
    ifs.open("some file.dat", std::ios::in | std::ios::binary);
    // read the integer
    int i;
    ifs.read((char*)&i, sizeof(int));
    // read the character
    char c;
    ifs.read((char*)&c, sizeof(char));
    // read the string
    int l;   // first read the length
    ifs.read((char*)&l, sizeof(int));
    // allocate memory (+1 for NULL terminator)
    char* txt = new char[l + 1];
    // read the text
    ifs.read(txt, l * sizeof(char));
    txt[l] = '\0';  // append NULL terminator
    std::string s(txt);   // and create a string
    delete[]txt;    // release text memory
    ifs.close();
    // and view the results
    std::cout << "i=" << i << "\n";
    std::cout << "c=" << c << "\n";
    std::cout << "s=" << s << "\n";
}

A closer look at these samples shows what we mean with file format. The fact that we saved in strict order an integer, a character and a string, and the way we saved the string in particular, is characteristic. Following the same strategy when writing and reading a file regardless of if it is a text or a binary file make our program less prone to errors and easier to maintain and debug.

Summary

In this part we covered the Input / Output system in the C++ Standard Library. We were introduced to:

The C++ Programming Language