Programming Workshop 2 (CSCI 1061U)
Faculty of Science, Ontario Tech University
C++ provides iostream library to read from and write to files. iostream library defines three data types to constructs i/o streams attached to files.
Data Type | Description |
---|---|
ofstream | This data type represents the output file stream and is used to create files and to write information to files. |
ifstream | This data type represents the input file stream and is used to read information from files. |
fstream | This data type represents the file stream generally, and has the capabilities of both ofstream and ifstream which means it can create files, write information to files, and read information from files. |
Once an file (IO) stream is ready, it behaves similarly to standard
streams that you are already familiar with, such as cin
,
cout
and cerr
, with a few caveats that we
discuss below.
Note: To perform file processing in C++, header
files <iostream>
and <fstream>
must be included in your C++ source file.
Use
("filename.txt"); ifstream f
or
;
ifstream f.open("filename.txt"); f
To setup f
as the read stream from file
filename.txt
. See the code below.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
("names.txt");
ifstream fif (f.is_open()) {
<< "Successfully opened \"names.txt\" for reading.\n";
cout
// Read contents here
.close(); // Don't forget to close the file stream
f}
else {
<< "Error opening \"names.txt\" for reading.\n";
cerr }
return 0;
}
Say we want to read the following file:
1342
23 354
This file contains 3 numbers. Each number is on its own line.
Use getline()
method
...
if (f.is_open()) {
;
string linewhile ( getline(f, line) ) // getline() returns a true if
// it successfully reads a line
// from the file; otherwise, it
// returns false.
{
<< line << '\n';
cout }
.close(); // Don't forget to close the file stream
f}
...
The above program will output
1342
23
354
What will happen if the numbers are stored as
1342 23 354
What will be the output of the above program (choice 1)?
Use extraction operator >>
...
if (f.is_open()) {
while ( !f.eof() ) // eof() method checks wether we
// have reached the end-of-file.
{
int num;
>> num;
f << num << '\n';
cout }
.close(); // Don't forget to close the file stream
f}
...
The above program will output
1342
23
354
Lets repeat the experiment. What will happen if the numbers are stored as
1342 23 354
What will be the output of the above program (choice 2)?
Is it the same output? If not, why not?
Use
("filename.txt"); ofstream f
or
;
ofstream f.open("filename.txt"); f
See the code below
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
("names.txt");
ofstream fif (f.is_open()) {
<< "Successfully opened \"names.txt\" for writing.\n";
cout
// Writes contents to the file here
.close(); // Don't forget to close the file stream
f}
else {
<< "Error opening \"names.txt\" for writing.\n";
cerr }
return 0;
}
Lets save a list of numbers to a file using insertion operator
<<
.
...
if (f.is_open()) {
<< 3.14159 << '\n';
f << 8321 << '\n';
f
.close(); // Don't forget to close the file stream
f}
...
When you compile and run this program one of the following two will happen:
The contents of this file will be
3.14159 8321
The above code overwrites the file everytime it opens it for writing. We can solve this issue by telling the system to open file in the append mode. Replace
("numbers.txt"); ofstream f
with
("numbers.txt", ios::app); ofstream f
ios::app
flag sets up ofstream for appending.
Output in C++ may be buffered. This means that anything that is
output to a file stream may not be written to disk
immediately. Instead several writes may be group together to
improve performance (disk access is painfully slow). Typically buffered
writes is not an issue; however, it can lead to problems in unique
circumstances. For example, if a program crashes. The contents may not
yet be written to the file and all changes may be lost. It is possible
to force a write by flushing. Use flush()
to force
a write to the disk.
Note that closing a file also flushes a buffer, forcing a write to the disk of course.
Interestingly, std::endl
which is often used in place of
\n
also forces a flush. Relying upon std::endl
instead of \n
to indicate a newline, for example, can
adversaly effect the performance. Interesting, eh!
<< "CSCI 1061U\n" << "Programming workshop 2\n";
f .flush(); // forces a save to the file f
Alternately,
<< "CSCI 1061U\n" << "Programming workshop 2" << endl; f
Note: In practice, it is preferrable to use
flush()
. This gives a clear signal that you intend to force
a write-to-the-file.
File stream constructors take an optional second parameter that
allows you to specify information about how the file should be opened.
This parameter is called mode, and the valid flags that it accepts live
in the ios
class.
ios file mode | Meaning |
---|---|
app | Opens the file in append mode |
ate | Seeks to the end of the file before reading/writing |
binary | Opens the file in binary mode (instead of text mode) |
in | Opens the file in read mode (default for ifstream) |
nocreate | Opens the file only if it already exists |
noreplace | Opens the file only if it does not already exist |
out | Opens the file in write mode (default for ofstream) |
trunc | Erases the file if it already exists |
It is possible to specify multiple flags by bitwise ORing them
together (using the |
operator).
ios:binary
mode flag can be used to open a file for
read/write in binary mode.
Insertion <<
and extraction >>
operators are not meant to be used for binary mode files.
To understand the difference between the two modes, consider the
following example. Say you want to write 3.14159 to a file. In text
mode, this can be accomplished as f << 3.14159
. The
<<
operator will write ‘3’, ‘.’, ‘1’,‘4’,‘5’,‘9’ to
the file, which will take 7 bytes. In binary mode, however, it will only
take 32 bits (or 4 bytes), since it will be written as a double.
You’ll use read
and write
methods to
read/write from binary files.
double pi = 3.14159;
("pi.dat", ios::binary);
ofstream f.write(&pi, sizeof(double)); f
and
double d;
("pi.dat", ios::binary);
ifstream f.read(&d, sizeof(double)); f
If there is a read error gcount()
can be used to see how
many bytes are actually read.
Source code showing storing and loading binary values to a file.
It is possible to set the location in the file where you want to perform the next read or write.
f.seekg( n );
It is also possible specify the beginning of
a file using ios::beg
.f.seekg( n, ios::cur );
f.seekg( n, ios::end );
f.tellg();
seekp()
and tellp()
can be used to move
the write locations.Check out the following code that showcases the use of opening a file for both reading and writing.
Note that ios::in
and ios::out
flags are
used together to specify that we plan to both read from and write to
this file stream.
<< "\nTask 4: change 50th number to 50000" << endl;
cout ("data", ios::binary | ios::in | ios::out);
fstream f4if (f4.is_open()) {
int d = 50000;
<< d << endl;
cout
.clear();
f4.seekg(0);
f4<< f4.tellg() << endl;
cout
.seekp(sizeof(int)*49, ios::beg);
f4<< f4.tellp() << endl;
cout
.write((char*)&d, sizeof(int));
f4.flush();
f4
if (f4.fail()) {
<< "Error writing.";
cerr }
.close();
f4}
else {
<< "Error opening to change 50th number\n";
cerr }
Source code that show cases reading and writing values to a file.
Use f.eof()
to check whether the end-of-file has been
reached as seen below
std::ifstream f("unknown.txt");
if (f.is_open()) {
char c;
while (!f.eof()) {
>> c;
f std::cout << "Read: " << c << std::endl;
}
}
else {
std::cout << "Error opening unknown.txt file" << std::endl;
}
If unknown.txt
contains
abcde f
gh i j k
Then the above code will print
Read: a
Read: b
Read: c
Read: d
Read: e
Read: f
Read: g
Read: h
Read: i
Read: j
Read: k Read: k
Note that the last character is printed twice. Can you figure out why? This is because end-of-file flag is set after
>> c; f
So while end-of-file flag is set, the code still prints out the
stale value in c
in the next line
std::cout << "Read: " << c << std::endl;
>> c;
f if (f.good()) {
std::cout << "Read: " << c << std::endl;
}
Here we check if the read operation has succeeded. This can be achieved by check if an error or fail bit was set on the filestream.
Another option is to use f.seekg()
to go to the
end-of-file and calculate its size. This is rarely used in practice.
stringstream
It is also possible to set up a stringstream object. This is used to
read and write values to/from strings. The following code demonstrates
stringstream
in action.
#include <sstream>
;
stringstream s<< "x = " << 22;
s
<< "1. Contents of the stringstream object." << endl;
cout << s.str() << endl;
cout
<< "\n2. Reading from stringstream object.";
cout char buf[32];
.getline(buf, 32);
sint actual_read = s.gcount();
<< "Read " << actual_read << " characters." << endl;
cout << "buf = \n"
cout << buf << endl;
<< "\n3. Setting contents of a stringstream object." << endl;
cout .str("Hello world");
s<< s.str() << endl; cout
Source code shows stringstream usage.