Table of Contents
File Input and Output
The information in this topic relates to file input/output operations used before C++17. See filesystem for information about the filesystem library included with C++17.
File input and output operations in C++ are almost identical to normal input and output operations. There are 3 file I/O classes.
- class ifstream derived from class istream
- ofstream class derived from ostream class
- fstream class derived from the iostream class
To use these classes you need to include the fstream header file.
Unlike off-the-shelf streams like clog, cerr, cin and cout, file streams need to be set up by us.
- To open a file for reading and/or writing, create an object of the appropriate file I/O class using the name of the file as a parameter.
- Then use the add («) or subtract (») operator to write data to or read data from the file.
- When you are done, do not close a file.
It can be summarized as follows.
File Output
In the following example we will use the ofstream class to output a file.
- main.cpp
#include <fstream> #include <iostream> int main() { // ofstream dosya yazmak için kullanılır // Deneme.txt adında bir dosya oluşturacağız std::ofstream outf{ "Deneme.txt" }; // Eğer çıktı dosya akışını yazmak için açamazsak if (!outf) { // Bir hata yazdırın ve çıkın std::cerr << "Siktir, Deneme.txt yazmak için açılamadı!\n"; return 1; } // Bu dosyaya iki satır yazacağız outf << "Satir 1\n"; outf << "Satir 2\n"; return 0; // when outf goes out of scope, ofstream // will close the destructive file }
File Input
We will use the ifstream class to read the file we wrote in the previous topic.
- main.cpp
#include <fstream> #include <iostream> #include <string> int main() { // ifstream dosyaları okumak için kullanılır // Deneme.txt adlı bir dosyadan okuyacağız std::ifstream inf{ "Deneme.txt" }; // Eğer çıktı dosya akışını okumak için açamazsak if (!inf) { // Bir hata yazdırın ve çıkın std::cerr << "Siktir, Deneme.txt okunmak için açılamadı!\n"; return 1; } // Hala okunacak şeyler varken while (inf) { // dosyadan bir dizeye bir şeyler oku ve yazdır std::string strInput; inf >> strInput; std::cout << strInput << '\n'; } return 0; // inf kapsam dışına çıktığında, ifstream // yıkıcı dosyayı kapatacaktır }
The example above may not give us the exact result we want. Because as we know, the subtraction operator is truncated on space characters.
So we can use the getline()
function.
- main.cpp
#include <fstream> #include <iostream> #include <string> int main() { // used to read ifstream files // We will read from a file called Trial.txt std::ifstream inf{ "Deneme.txt" }; // If we cannot open the output file stream for reading if (!inf) { // Print an error and exit std::cerr << "Shit, Deneme.txt could not be opened for reading!\n"; return 1; } // While there's still something to read while (inf) { // read and print something from a file to a string std::string strInput; std::getline(inf, strInput); std::cout << strInput << '\n'; } return 0; // When inf goes out of scope, ifstream // will close the destructive file }
Bumper Out
Output can be buffered in C++. This means that anything that is output to a file stream may not be immediately written to disk. Instead, several output operations can be combined and processed together. This is primarily done for performance reasons. When a buffer is written to disk, this is called a buffer flush. One way to ensure that the buffer is flushed is to close the file - the contents of the buffer will be written to disk and then the file will be closed.
Buffering is usually not a problem, but in some cases it can cause complications for the careless.
The main culprit in this case is that there is data in the buffer and then the program terminates immediately (either by crashing or by calling the exit()
function).
In these cases, the destructors of the file stream classes are not executed, which means that files are never closed and buffers are never emptied.
In this case, the data in the buffer is not written to disk and is lost forever. For this reason, it is always a good idea to close open files before calling the exit()
function.
It is possible to flush the buffer manually using the ostream::flush()
function or by sending std::flush
to the output stream. Both of these methods ensure that the contents of the buffer are immediately written to disk in case the program crashes.
std::endl
flushes the output stream. Overuse of std::endl
(causing unnecessary buffer flushes) can cause performance impacts when doing buffered I/O where flushes are expensive (such as writing to a file).
For this reason, performance-conscious programmers often use \n%
instead of std::endl
to add a new line to the output stream, to avoid unnecessary flushing of the buffer.
File Modes
The constructors of the file stream classes have an optional second parameter called file mode that allows us to specify information about how the file should be opened.
The flags accepted by this parameter are in the ios
class.
ios file mode | Description |
---|---|
app | Opens file in append mode |
ate | Searches to the end of the file before reading/writing |
binary | Opens file in binary mode |
in | Opens the file in read mode (default for ifstream) |
out | Opens file in write mode (default for ofstream) |
trunc | Deletes the file if it already exists |
It is possible to specify multiple flags using the |
operator.
Due to the way fstream
is designed, it may fail if std::ios::in
is used and the file being opened does not exist. If you need to create a new file using fstream
, just use std::ios::out
mode.
Random File I/O
All file streams have a file pointer. This pointer keeps track of the current file read/write location.
In general, when a file is opened for read/write, this pointer is located at the beginning of the file. If we have opened it in app
file mode, it is moved to the end of the file so that the data we are going to add will be added at the end of the file.
Up to this point in the article, the I/O operations have been sequential. If we want, we can randomly access the parts of the file we want. For this we need to move the pointer to the desired location.
We do this by manipulating the file pointer with the seekg()
and seekp()
functions.
In normal streams, seekg()
manipulates the read position and seekp()
the write position separately. However, in the case of file I/O, the read and write positions are shared. So both functions can be used interchangeably.
These two functions have two parameters;
- The first parameter is an offset that determines how many bytes to move the file pointer.
- The second parameter is an ios flag that specifies what the offset parameter should be offset by.
ios flag | Description |
---|---|
ios::beg | Offset from the beginning of the file |
ios::cur | Offset from the current location of the file |
ios::end | Offset from the end of the file |
Positive offset means moving the file pointer towards the end of the file, while negative offset means moving the file pointer towards the beginning of the file.
- main.cpp
#include <fstream> #include <iostream> #include <string> int main() { std::ifstream inf{ "Deneme.txt" }; // If we could not open the input file stream for reading if (!inf) { // Print an error and exit std::cerr << "Shit, Deneme.txt file could not be opened for reading!\n"; return 1; } std::string strData; inf.seekg(5); // Switch to 5th character // Take the rest of the line and go to line 2 and print std::getline(inf, strData); std::cout << strData << '\n'; inf.seekg(8, std::ios::cur); // move another 8 bytes to file // Take the rest of the line and print std::getline(inf, strData); std::cout << strData << '\n'; inf.seekg(-14, std::ios::end); // move 14 bytes before the end of the file // Take the rest of the line and print std::getline(inf, strData); std::cout << strData << '\n'; return 0; }
Other important functions are tellg()
and tellp()
. These functions return the absolute position of the file pointer.
With these functions we can calculate the size of the file.
std::ifstream inf {"Deneme.txt"}; inf.seekg(0, std::ios::end); // move to end of file std::cout << inf.tellg();
In programming \n%%
is actually an abstract concept. We interpret this character as a line break.
- In Windows, the end of a line is represented sequentially by the characters CR (line head) and LF (line feed). (That takes up 2 bytes of storage space.)
- On Unix, a newline is represented only by the LF (line feed) character (That takes up 1 byte of storage)
This is why the example code above will give different results on the two operating systems.
Read and write simultaneously with fstream
As we know, the fstream class supports both write and read operations. The major flaw here is that it is not possible to arbitrarily switch between reading and writing.
If we want to switch from one read or write operation to the other, we need to modify the file pointer.
If we don't want to change the current position of the file pointer, i.e. if we just want to move on to the next operation, we need to move it back to the position where it is already located with seek
.
// Suppose iofile is an object of type fstream iofile.seekg(iofile.tellg(), std::ios::beg); // Search to current file location
If you do not take this action
While it is quite easy to export variables to a file, things get more complicated when it comes to pointers.
As we know, pointers hold memory addresses and these addresses can change every time the program runs.
If we write pointers to disk, it will write the current address at the time of execution.
Do not write memory addresses to files. Variables that were originally at these addresses may be at different addresses when you read their values back from disk, and the addresses will be invalid.
Taken from UCH Viki. https://wiki.ulascemh.com/doku.php?id=en:cs:cpp:common:fileio