Part C - Encapsulation
Custom File Operators
Describe the file stream classes of the standard input output library
Describe the syntax for creating and using file objects with fundamental types
Overload the extraction and insertion operators for file objects and compound types
"Files are examples of containers that you can
both read from and write to. Consequently, you can have a
stream that supports both << and >>." (Stroustrup, 1997).
File Classes
| File Objects
| Fundamental Types
| Custom Types
| Nice To Know
| Exercise
The extraction and insertion operators, which are overloaded for the standard
input and output objects in the iostream library, are
also overloaded for file objects. The overloads for file objects cover
all of the fundamental types as right operands. We can overload the
operators for objects of compound type as right operands in the same way that
we did in the preceding chapter for the standard input and output objects.
This chapter introduces the stream classes that manage the transfer of
information to and from files. This chapter describes how to create
file objects from these classes, how to read and write data of fundamental
type and how to overload the operators for file objects as left operands
and objects of custom compound type as right operands.
File Stream Classes
The input-output library defines three classes for managing the
transfer of 8-bit character streams between secondary storage and
system memory:
- ifstream - input from a file
- ofstream - output to a file
- fstream - input from and output to a file
The fstream system header file includes the
class definitions:
The classes are defined within the std namespace.
They provide access to the stream through separate input and output buffers.
State Methods
The queries available for interrogating a file object
include:
- bool is_open() const - the file stream is open
- bool good() const - the file stream is ready to process
- bool fail() const - the file object failed
- bool eof() const - the file object encountered an end of file mark
- bool bad() const - the file object encountered a serious error
If a file object is not in a good() state, we
must reset its state. To reset
its state we call:
- void clear() - resets the file object to a good state
These member functions operate in the same way that they operate with the standard input and output objects
described in the chapter entitled Input and Output Examples.
File Objects
A file object is an instance of one of the file stream classes. The object
when used with the insertion or extraction operators processes formatted data.
The object uses the platform's encoding sequence (ASCII, EBCDIC, Unicode) to convert
between stream bytes and data stored in system memory.
File Connection
A file object connects to a file for reading and writing in similar
ways. The object's destructor closes the file before disconnecting.
Input File
Defining an instance of the ifstream class
creates a file object for reading. This class defines a
no-argument constructor and one that takes the name of the file in the
form of a C-style null-terminated string.
For example,
#include <fstream>
std::ifstream fin("input.txt"); // connect fin to input.txt for input
|
To connect a file to an object defined using the no-argument constructor to a file, we
call the open() member function.
For example,
#include <fstream>
std::ifstream fin; // defines an empty file object named fin
fin.open("input.txt"); // connect input.txt to fin
|
Output File
Defining an instance of the ofstream class
creates a file object for writing. This class defines both a
no-argument constructor and one that takes the name of the file in the
form of a C-style null-terminated string.
For example,
#include <fstream>
std::ofstream fout("output.txt"); // connect fout to output.txt for output
|
To connect a file to an object defined using the no-argument constructor, we
call the open() member function.
For example,
#include <fstream>
std::ofstream fout; // create an empty file object named fout
fout.open("output.txt"); // connect output.txt to fout
|
Confirming the Connection
The is_open() member function on a file
object returns the current state of the connection:
#include <iostream>
#include <fstream>
std::ofstream fout("output.txt"); // connects output.txt to fout for output
if (!fout.is_open()) {
std::cerr << "File is not open" << std::endl;
} else {
// file is open
}
|
Fundamental Types
The syntax for reading from a file and writing to one is the same as the
syntax for reading from the standard input object and writing to a standard
output object (see the chapter entitled Input and Output
Examples).
The standard input-output library contains the overloaded definition of
the extraction and insertion operators for each of the file stream classes.
Reading From a File
A file object reads a formatted connection using the extraction operator in the same way
as the standard input object (cin) uses this
operator.
For example, consider a file with a single record: 12 34 45 abc
The output from the following program is shown on the right:
// Reading a File
// readFile.cpp
#include <iostream>
#include <fstream>
int main() {
int i;
std::ifstream f("input.txt");
if (f.is_open()) {
while (f.good()) {
f >> i;
if (f.good())
std::cout << i << ' ';
else if (!f.eof())
std::cout << "\n**Bad input**\n";
}
}
}
|
12 34 45
**Bad input**
|
Writing to a File
A file object writes to its connection under format control using the
insertion operator in the same way as the standard output objects
(cout, cerr and
clog).
For example, the contents of the file created by the following program
are shown on the right
// Writing to a File
// writeFile.cpp
#include <iostream>
#include <fstream>
int main() {
int i;
std::ofstream f("output.txt");
if (f.is_open()) {
f << "Line 1" << std::endl; // record 1
f << "Line 2" << std::endl; // record 2
f << "Line 3" << std::endl; // record 3
}
}
|
Line 1
Line 2
Line 3
|
Custom Types
Insertion and extraction operators that have been overloaded for
standard output and input objects respectively as left operands and
a custom compound type as the right operand work without modification
with file objects as left operands. This flexibility has to do with inheritance, which is described
later in the chapter entitled Functions in a Hierarchy.
Neither the header file nor the implementation file require any modification.
Since accepting input from a file does not involve the interaction that we
expect across a standard input device, we typically overload the extraction
operator to work differently with file objects. We overload the operator
for an ifstream object as the left operand.
For example, we add the prototype for the file extraction helper
to the definition of our Student class:
// Student.h
#include <iostream> // for std::ostream, std::istream
#include <fstream> // for std::ifstream
const int M = 13;
class Student {
int no;
char grade[M+1];
public:
Student();
Student(int);
Student(int, const char*);
void display(std::ostream& os) const;
Student& operator+=(char);
bool empty() const { return no == 0; }
int number() const { return no; }
const char* marks() const { return grade; }
};
std::istream& operator>>(std::istream& is, Student& s);
std::ifstream& operator>>(std::ifstream& is, Student& s);
std::ostream& operator<<(std::ostream& os, const Student& s);
bool operator==(const Student&, const Student&);
Student operator+(const Student&, char);
Student operator+(char, const Student&);
|
The implementation file contains the definition of the file extraction
operation for our Student class:
// Student.cpp
#include <cstring>
#include "Student.h"
Student::Student() {
no = 0;
grade[0] = '\0';
}
Student::Student(int n) {
*this = Student(n, "");
}
Student::Student(int n, const char* g) {
// see Current Object chapter for validation logic
no = n;
std::strcpy(grade, g);
}
void Student::display(std::ostream& os) const {
os << no << ' ' << grade;
}
Student& Student::operator+=(char g) {
int i = strlen(grade);
if (i < M) {
// add validation logic here
grade[i++] = g;
grade[i] = '\0';
}
return *this;
}
std::ostream& operator<<(std::ostream& os, const Student& st) {
st.display(os);
return os;
}
std::istream& operator>>(std::istream& is, Student& s) {
int no;
char grade[M+1];
// student number
std::cout << "Number : ";
is >> no;
// student grades
std::cout << "Grades : ";
is.ignore(); // swallow newline in the buffer
is.getline(grade, M+1); // read string with whitespace
Student temp(no, grade);
if (!temp.empty())
s = temp; // replace s only if not empty
return is;
}
std::ifstream& operator>>(std::ifstream& is, Student& s) {
int no;
char grade[M+1];
// student number
is >> no;
is.ignore(); // skip whitespace
// student grades
is.getline(grade, M+1); // read string with whitespace
Student temp(no, grade);
if (!temp.empty())
s = temp; // replace s only if not empty
return is;
}
|
Note the difference between the overloaded definitions for the extraction operator.
The ifstream definition omits the user prompts.
The client file that uses this upgraded Student
class creates the file objects, writes to them and reads from them:
// Custom File Operators
// customFile.cpp
#include "Student.h"
int main ( ) {
Student harry(975, "AABD"), josee(976, "BAAA");
std::ofstream oufile("Student.txt");
oufile << harry << std::endl;
oufile << josee << std::endl;
oufile.close();
std::cout << harry << std::endl;
std::cout << josee << std::endl;
std::ifstream infile("Student.txt");
infile >> harry;
infile >> josee;
harry += 'B';
josee += 'C';
std::cout << harry << std::endl;
std::cout << josee << std::endl;
}
|
975 AABD
976 BAAA
975 AABDB
976 BAAAC
|
The records written to the Student.txt file
by this program are:
Nice To Know
Open-Mode Flags
A file object's connection mode we be customized through combinations of flags
passed as the second argument to the object's constructor or its open()
method.
Six flags define the connection mode:
- std::ios::in open for reading
- std::ios::out open for writing
- std::ios::app open for appending
- std::ios::trunc open for writing, but
truncate if file exists
- std::ios::ate move to the end of the
file once the file is opened
Practical combinations of these flags include
- std::ios::in|std::ios::out open for reading
and writing (default)
- std::ios::in|std::ios::out|std::ios::trunc open
for reading and overwriting
- std::ios::in|std::ios::out|std::ios::app open
for reading and appending
- std::ios::out|std::ios::trunc open for
overwriting
The vertical bar (|) stands for 'or' and
is called the bit-wise or operator.
The Defaults
The default combinations are:
- ifstream - std::ios::in open for reading
- ofstream - std::ios::out open for writing
- fstream - std::ios::in|std::ios::out open
for reading and writing
The Logical Negation Operator
The logical negation operator (!) is overloaded
in the standard input-output library as an alternative to the fail() query.
This operator reports true if the latest operation
failed or if the stream has encountered a serious error.
We can apply this operator to any stream object to check the
success of the most recent action:
if (fin.fail()) {
std::cerr << "Read error";
fin.clear();
}
|
if (!fin) {
std::cerr << "Read error";
fin.clear();
}
|
The logical negation operator (!)
applied to a file object returns the state of the connection:
#include <iostream>
#include <fstream>
std::ofstream fout("output.txt"); // connects fout to output.txt for output
if (!fout) {
std::cerr << "File is not open" << std::endl;
} else {
// file is open
}
|
Rewinding a Connection
istream, fstream
To rewind an input stream we call:
- istream& seekg(0)
- sets the current position in the input stream to 0
ostream, fstream
To rewind an output stream we call:
- ostream& seekp(0)
- sets the current position in the output stream to
0
Closing a Connection Early
To close a file connection before the file object
goes out of scope, we call the close()
member function on the object:
// Concatenate Two Files
// concatenate.cpp
#include <fstream>
int main() {
std::ifstream in("src1.txt"); // open 1st source file
std::ofstream out("output.txt"); // open destination file
while (!in.eof())
out << in.get(); // byte by byte copy
in.clear();
in.close(); // close 1st source file
in.open("src2.txt"); // open 2nd source file
while (!in.eof())
out << in.get(); // byte by byte copy
in.clear();
}
|
Writing to and Reading from the Same File
A file object of the fstream class
can write to a file and read from that same file.
For example, the output for the following program is
shown on the right
// File Objects - writing and reading
// fstream.cpp
#include <iostream>
#include <fstream>
int main() {
std::fstream f("file.txt",
std::ios::in|std::ios::out|std::ios::trunc);
f << "Line 1" << std::endl; // record 1
f << "Line 2" << std::endl; // record 2
f << "Line 3" << std::endl; // record 3
f.seekp(0); // rewind output
f << "****"; // overwrite
char c;
f.seekg(0); // rewind input
f << std::noskipws; // don't skip whitespace
while (f.good()) {
f >> c; // read 1 char at a time
if (f.good())
std::cout << c; // display the character
}
f.clear(); // clear failed (eof) state
}
|
**** 1
Line 2
Line 3
|
Summary
- the extraction and insertion operators are overloaded for file objects as left operands and fundamental types as right operands
- an input file object is an instance of an ifstream class
- an output file object is an instance of an ofstream class
- the extraction and insertion operators may be overloaded for file objects as left operands and custom compound types as right operands
Exercise
|