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
- We can create a file
- Write data to the file
- Append data to the file
- Modify/update data in a file
- Read data from the file
- Delete the file
- Copy or move the file to another location
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.
ofstream | This is the output file stream. It is used to create files and write information. |
ifstream | This is the input file stream. It is used to read information from existing files. |
fstream | This 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::app | Open the file for appending data |
ios::ate | Open the file for appending data and move rea/write control to the end |
ios::in | Open the file for input |
ios::out | Open the file for output |
ios::trunc | Open the file and if it exists truncate its contents |
Ios::binary | Open 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:
- Streams
- String streams
- File streams
- File types
- Opening and closing files
- Using text files
- Using binary files