28 Working with Files in C++

Jyoti Pareek

Introduction

 

So far, we have used predefined standard I/O streams cin and cout to read input from standard input device and write output to standard output device. Here data storage is transient. We will now extend this concept to disk files.

 

Files are used for persistent data storage. Most of the applications require storing data in files and use it later. Files usually resides on secondary storage devices like disk. In this module, we will see input/output operations using file streams.

 

1.      File

 

File is used for persistent data storage. It resides on non- volatile secondary storage devices to retain the data for longer time. Data in files gets lost only when someone erases the data or when hardware storage media fails due to some reasons. Each file has End-Of-File (EOF) marker and it varies as per operating system. Actually all files are managed by operating system.

 

2.  Need of file

 

Terminal I/O using streams cin and cout stores the data in iostreams temporarily. Data in these streams are lost when the execution of an application gets terminated or when power fails. To have the data available even after program termination, it should be stored in files on non-volatile memory devices like disks. In most of the applications, the data needs to be retained for later use. Data may be created by one program and used by many programs. For example, name and registration number of students may be used by applications in accounts department, exam department. In railway reservation system, it requires to store the details about trains, schedule, fare, ticket booking etc. in persistent storage.

 

Sometimes when data size is large, one may like to create data in file and then use it. For example, giving input for a small matrix of size 10 x 10 requires 100 elements to be entered. A small mistake while entering data may require repeating entry of all elements. Here, it is better to create one text file and enter 100 elements in it. These data can be read by program to perform matrix operation. It will help to reduce data entry time and the chances of errors.

 

3.  File Streams

 

Working with files requires the use of file-oriented classes: ifstream for input, ofstream for output and fstream for both input and output. Class ifstream (input file stream) is derived from istream, ofstream (output file stream) is derived from ostream and fstream (file stream) is derived from iostream. The ancestor classes (istream, ostream and iostream) are in turn derived from class ios. The file-oriented classes are also derived from the fstreambase class with multiple inheritances. This fstreambase class contains an object of class filebuf, providing a file-oriented buffer and its associated member functions. Class filebuf is derived from the more general streambufclass.Classesifstream, ofstream and fstream are declared in the fstream file. Thus, to perform file processing in C++, header files <iostream> and <fstream> must be included in C++ source file.ofstreamobject represents the output file stream and is used to create filesand/or write information to files. ifstream object represents the input file stream and is used to readinformation from files.fstream object represents the file stream having capabilitiesof both ofstream and ifstream which means it can create files, writeinformation to files, and read information from files.

 

4.      Differences between a predefined standard stream and a file stream

 

·      A file stream needs to be created and connected to a file before it can be used. The predefined streams are available to all programs and can be used right away.

·      Even though read/write can be performed from any position with basic iostreams; this usually does not make any sense with the predefined streams, as they are connected to the sequential devices by default. One can reposition a file stream to arbitrary file positions and access data randomly also.

 

5.      File operations

 

When working with files, it requires file operations like connecting the physical file with file stream, opening the file, reading/writing, checking errors and closing the file.

//  basic file operations #include <iostream> #include <fstream> usingnamespacestd;

int main ()

 

{

ofstreamoutfile;

outfile.open (“example.txt”);

outfile<< “This is my first attempt to use a file\n”;

outfile.close();

return 0;

}

 

As file stream classes are derived from i/o stream classes, it is easy to read/write from files. All the members of base classes are available to file classes. All of the member functions and operators that one can apply to istream or ostream or iostream object can also be applied to ifstream and ofstream and fstream objects. Thus reading and writing operations using file stream objects are very similar to reading/writing using standard streams cin and cout.As an extension of the base classes, file streams have some additional member functions and internal information.Internally file objects use a special kind of stream buffer, called a file buffer, to control transferring characters to/from the associated file.

 

5.1.    Opening a file

 

Before performing input/output operations on file, it requires a file to be opened. When opening a file, it requires supplying the physical file name and its location. The operating system (OS) searches its file system for the file of that name or create file with specified name.

 

The OS will look for a file with specified name in thesame directory as of executable C++ files or project. If the file is placedin other directory, it should be specified with exact path.

A file can be opened in two ways:

 

1.     Using parameterized constructor – Open a file on construction of a file stream

2.     Using open () function to connect a file with existing file stream

 

5.1.1. Open a file on construction of a file stream using parameterized constructor

 

While creating file stream object, specify a file name with file path as argument. It performs both the tasks of creating a file stream and connecting it to a specified file in a single statement.If an operation fails due to some reason, file stream is not ready for use.

 

Example:

ifstreaminfile (“inp.dat”); //create file stream infile, connect to file named ‘inp.dat’ if (!infile) // check error: unable to open file for input File will be closed automatically on destruction of the stream object.

 

5.1.2. Using open () function to connect a file with existing file stream

 

Here, it requires two statements: first to create file stream and second to connect the file.

 

1.      Create anempty file stream using constructor without parameter. Here, a file stream is created that is not connected to any file. Any operation on the file stream will fail.

2.      When needed, invoke open() function with file name with file path as parameter to connect it to the file stream.If the specified file cannot be opened due to some reason, failbit will be set; otherwise file stream is ready for use.

 

Example:

 

ifstreaminfile; \\ 1- file stream infile is created, but not connected to any file …

infile.open(“inp.dat”); \\ 2 – file ‘inp.dat’ is opened and connected to file stream infile if (!infile) // error: unable to open file for input

 

Advantages of using this method of creating file first and connecting later are as follows:

 

·      Once a stream is created, it can be used to connect to any file later using open().

·      It enables the programmer to bind a file stream to more than one file during the lifetime of a stream.

·      Closing the file does not destroy the stream. It simply gets disconnected from the physical file. Stream is still available for another physical file to get connected.

 

5.1.3. File open mode

 

Files are opened using default mode when modeis not specified. It defaults to text file with input mode for ifstream; output mode for ofstream and input as well as output modes for fstream. Sometimes, it may be required to change this default. For example, one may want to have binary I/O; or want to append data in existing file. For this, C++ provides various modes that can be used while opening the file. Open mode is defined in ios_base class.The ios_base::openmode is of a bitmask type like the format flags and the stream state. The bits and the meaning of open mode are as follows:

 

Open mode bit Meaning
ios_base::in Open file for reading. File must exist. Default for
ifstream.
ios_base::out Open file for writing. If file exists, existing file is
deleted and a new empty file is created with same
name. If file does not exist, it will create a new
file. Default for ofstream.
ios_base::ate Start position for reading or writing is at end of
file (AT End).
ios_base::app Start  position for  writing is  at  end of  file
(APPend)
ios_base::trunk Truncate  file  to  zero  length  if  it  exists
(TRUNCate)
ios_base::binary Open file in Binary mode for binary I/O
ios_base::nocreate Do not allow to create a new file. When opening,
file must exist. If the file to be opened does not
exist, an error will occur.
ios_base::noreplace Do not allow to replace the content of file. Error
will occur when opening file for output if file
already exists, unless ate or app is set.

 

Default file open mode is as follows:

File Stream Default Open Mode
ifstream in
ofstream out|trunc
fstream in|out|trunc

 

File open mode flag bits can be combined for effects of multiple flags using OR(|). For example, ios::out|ios::app  Open file for writing by appending new content. It creates file if it does not exist. If file already exists, file pointer is positioned at the end.

ios::in|ios::out

Open file for reading as well as writing. File must exist.

ios::in|ios::out | ios::trunc

 

Open file for reading as well as writing. It empties fileif it exists, creates otherwise.

 

While opening the file, file name and open mode can be passed as two parameters in constructor as well as open() function.

 

·      Constructor: <file stream> (const char *filename, ios_base::openmode mode);

·      Function open(): void open(const char *filename, ios_base::openmode mode);

 

In the given examples, constructor and open() are used with file name only as an argument. In absence of second argument, default value of file open mode is considered depending upon the type of file stream object.One can explicitly specify the second argument for open mode in the file stream constructor while creating file stream object or in open() call.

 

For example,

 

ofstreamfout(“out.txt”,ios_base::out|ios_base::app);

ofstreamoutfile;

outfile.open(“students.txt”, ios::out|ios::app);

Note that class ios is derived from ios_base, so all the members of ios_base class are inherited in ios class.

5.1.4. Various ways to test whether a file is opened successfully or not

 

To test whether the file is successfully opened or not, one way is to use is_open() method. One may even check error state bits and use methods good(), bad(), fail(), rdstate() etc. as described with standard streams.

  • ·      Function is_open() returns true if open is successful
  • ·       Functions good()returns true if open is successful, bad() and fail() returns true when unsuccessful
  • ·      Operator !returns true when open is unsuccessful
  • ·      Stream pointer is null when open is unsuccessful

Examples:

 

fstreamfobj; fobj.open(“data.txt”, ios::in | ios::out);

  • ·      if (fobj.is_open()) {//process} else {//error}
  • ·      if (fobj.good()) {//process} else {//error}
  • ·      If (fobj.fail()) {//error} else {//process}
  • ·      If (fobj.bad()) {//error} else {//process}
  • ·      If (!fobj) {//error} else {//process}
  • ·      if (fobj) {//process} else {//error}

5.2.    Closing file stream

 

When a file stream object goes out of scope, it is destructed by an implicit call to the destructor and the file is closed. A file can be explicitly closedby invoking close() function. When a file is closed, the file stream becomes empty, until it is reconnected to another file.

 

To close the file, use close() member function as follows:

infile.close();  // explicitly closing file associated with infile file stream

It is asking the operating system to disconnect the file and save its final state. In other words, it disconnects the file stream from the physical file.

 

5.3.    Text I/O using files

 

In text I/O, input/output stream contains text data. In case of input, text data is read from input stream and transformed as per data type to be stored in memory; whereas in case of output, data from memory is transformed into text and stored in output stream.

 

Unless binary is specified as file open mode, file is considered to be for text I/O by default. With text I/O, a file always contains data in human readable character form; character may be ASCII or Unicode. Once the file is opened, one can read and write values from/to the streams using the stream input and output operators just like operations with cin and cout.  Let us consider file named “inp.txt” with the content 24 567.89. An easy way to prepare an input file is to use a text editor that creates plain ASCII text files. To avoid some odd end-of-file behaviour, be sure that the last character in the file is a whitespace character such as a newline.To use this text file, first create input file stream object and connect this stream to physical file inp.txt as follows:

 

inpFile = ifstream.open(“inp.txt”);

Above statement will create input file streamassociated with object inpFileand connect it to file named “inp.txt” for reading purpose. Note that the default file open mode is ios::in for ifstream. Once the file is opened successfully, it can be used for input operations. Since ifstream and ofstream inherit from istream and ostream, the definitions of overloaded operators << or >> for ostream and istream automatically work for file streams also. Here, to read an integer and a double value from the input file intovariables say intVar and dblVar using >> extraction operator, one can write following statement:

 

inpFile>>intVar>>dblVar;

 

Similarly to output to the text file named can be performed using << insertion operator. Assuming file “out.txt” connected with output file stream associated with outFile object, one may use << insertion operator as follows:

 

outFile<< “The integer is ” <<intVar<<endl;

outFile<< “The double is ” <<dblVar<<endl;

Once reading from or writing to a file is finished, it is good programming practice to close the file.

 

Since ifstreamis derived from istream, it inherits its member functions also to read data from input stream istream. Thus like extraction operator, one can also use get() or getline() functions to read characters or long sequence of characters such as lines. Various forms of functions get() and getline() are already explained in previous module while discussing I/O using console stream cin. To use these functions with input files, the only change to be made is to use input file stream object instead of cin object. In the same way, as ofstream is derived from ostream, it inherits member functions also to write data into output stream ostream. Like insertion operator, one can also use put() to write a character into output file stream.With files, one needs to use output file stream object in place ofcout object. Because all these stream classes are inherited form ios_base, stream error status and format flags are also inherited in file stream classes. Thus one can use iostream error bits as well as perform formatted input/output operations with file streams in a way similar to console I/O streams cin and cout. Functions good(), bad(), eof(), fail() etc. can also be used with file streams to check error after any file operation.

 

5.4.    Binary I/O using files

 

The numbers are stored in computer’s memory in binary format rather than as string of characters. Text I/O operations require a transformation between numeric values stored in memory and the corresponding sequence of characters. Binary I/O saves this transformation time and thus provides more efficiency. Another advantage is of saving space. In binary I/O an int is stored in 4 bytes, whereas its text version might be “123456789”, requiring 9 bytes. Thus using files in binary has the advantages of speed as well as space over text files. The only disadvantage is human readability. Such files cannot be read using text editors.

 

For binary I/O, file should be opened using the mode flag ios_base::binary. As an effect, it suppresses the formatting usually performed. Binary inputand output is done solely by using read() and write() functions of istream and ostream class respectively.

Reading/writing from/to binary file:

 

Syntax:

 

istream&istream::read(char* str, int count)ostream&ostream::write(const char* str, int count)

Function read() reads a block of binary data or reads a fixed number of bytes from the specified stream and store it in an C-type array str.

Function write() writes a block of binary data or writes fixed number of bytes from a specific memory location of strto the specified stream.

 

Note:

 

Both functions take two arguments.

·      The first is the address of variable and the second is usually the length of that variable in bytes. Theaddress of variable must be type cast to type char*pointer.

·      The data written to a file using write( ) can only be read accurately using read().

 

6.      Random Access in Files

 

Data from the file can be read sequentially as well as randomly from any position. Sequential data access may not be appropriate in some cases. For example, modify phone number of specific student, display balance amount of specified account number in a bank, search specified train and display its information etc. All this operations will be very slow when performed with sequential access. To modify phone number of 10th student requires first 9 student’s data to be read first.

 

Random access of data provides faster retrieval of records. Moving to 10th student record without reading first 9 will enable the updating faster.

 

File pointer Random access enables read/write data from any position. For random access, it requires first to place a read/write file pointer at the required position in the file and then perform read/write. The file pointer indicates the position in the file at which the next input/output is to occur. In C++, there are two types of file positions pointers: get and put. Get position pointer specifies location for next read and it is available for istream or input mode. Put position pointer specifies the location for next write operation and is available for ostream or output mode. Member functions to set and get the file pointer Moving the file pointer in a file may be required for various operations like modification of data, deletion of data, searching data etc. Both istream and ostream provide member functions for repositioning the file pointer and getting the current position of file pointer. Functions seekg() – (seek get) and seekp() – (seek put) are used to set file pointer for input and output respectively,Similarly member functions tellg() – (tell get) and tellp() – (tell put) are used to get file pointer for input and output respectively.Here,seekg() and tellg() are the member functions of istream; whereas seekp() and tellp() are member functions of ostream class.Functions tellg() and tellp() do not take any parameters. They return the current position of file pointer. Return value is of type streampos which is usually a long integer. Their syntax is as follows:

 

streamposistream::tellg()

streamposostream::tellp()

To know the file size (in bytes), tellg() or tellp() can be used as shown below.

fstreammyfile (“student.bin”, ios::binary | ios::ate);

cout<< “size is: ” <<myfile.tellg() << ” bytes\n”;

Functions seekg() and seekp() take two arguments: offset and seek direction. Their syntax is:

streamposistream::seekg(streamoffloc=0, ios::seekdirrel=ios::beg)

streamposostream::seekp(streamoffloc=0, ios::seekdirrel=ios::beg)

 

Here, second argument is the relative position of enum type ios::seekdir. It can take values ios::beg, ios::cur andios::end. Default value isios::beg denoting beginning of file. Values ios::cur denotes current position and ios::end denotes end of file.First argument is an integer value that specifies the offset location as number of bytes from relative position specified in second argument. Offset is of the type streamoff, normally a long integer. It may be –ve, 0 or +ve. Default value is zero. Negative value indicates backward move, whereas positive value indicates forward move.

 

Examples of using istream member function seekg() for moving file pointer:

 

•      inpFile.seekg()–Moves file pointer to beginning of the file. As arguments are not specified, it uses default values. Thus it has same effect as inpFile.seekg(0, ios::beg)

•      inpFile.seekg(n, ios:beg) – Moves file pointer to n bytes forward from beginning. It can be written as inpFile.seekg(n) also.

•      inpFile.seekg(n, ios:end) -Moves file pointer to n bytes back from end of the file.

•      inpFile.seekg(0, ios:end) – Moves file pointer to end of the file.

•      inpFile.seekg(n, ios:cur) – Moves file pointer to n bytes forward or back from current position depending on whether n is positive or negative.

 

Member function seekp() of ostream class can be used in the similar way for moving file pointer in output stream.Random access for search or update data is easier with fixed length records. For varying length records, one may need some ways to store the address of records in the data file and use these addresses as offset while searching records.

 

Let us consider simple example of using fixed-length record binary file and use random access. Consider a file having student’s records. Assume class student with attributes name, gender, birthdate,

phone; method input() asking the user to enter student’s details and method display() to show student details on screen.  Following is the code used to first create binary file ‘student.dat’ with student’s detail and then access a record at random.

 

ofstreamfout(“student.dat”, ios::binary);

 

//  store student’s data in file student s;

int n=30; // assume 30 students for (int i=0; i<n; i++)

{

s.input(); // input from user

 

fout.write (reinterpret_cast<const char *> (&s), sizeof(student)); // store

}

 

fout.close();

 

//  access 7th record

 

ifstream fin (“student.dat”, ios::binary);

 

fin.seekg( 6 * sizeof( studnet) ); // pointer at 7th record from beginning of file fin.read ((char*)&s, sizeof(s)); // may use reinterpret_cast for type conversion s.display();

 

Above code can be modified as per need. Readers may try to rewrite the code to update phone number of the specified student.

 

Summary

 

·      Files are to be used when there is large input or when the same data is needed by multiple programs.

·      Files store persistent data in non-volatile secondary storage devices.

·      Files are managed by operating system.

·      C++ provides file streams ifstream, ofstream and fstream which are inherited from istrem, ostream and iostream respectively.

 

o All operations that can be performed with standard streams (cin and cout) can be performed with file streams also. Thus input can be perfromed using extraction operator and member functions get, getline, read; output can be performed using insertion operator and methods like put and write.

o Error status flag and formatting flags are also used the same way as with standard streams.

·      As an extenstion, file stream classes provides some other methods to open file, to check whether file is opened successfully or not (is_open()) andclose file.

·      File streams are required to be created by programmer. They are not available like standard streams.

·      File streams can be created using parameterized as well as non-parameterized constructors. With non-parameterized constructor, it simply creates a stream that is not connected to any file. With parameterized constructor, it creates a stream and assocites it with physical file.

·      Files can be opened with various modes like in, out, app, ate, trunc, binary, nocreate, noreplace. By defaut,

o  files are considered for text i/o operations

o ifstream is considered for input opearions o ofstream is considered for output operations  fstream is considered for both input as well as output

  • Binary files are more efficient in terms of speed and memory usage. Read and write methods are to be used for binary read/write operations.
  • Files can be accessed randomly allowing faster retrieval of data. Methods seekg and seekp can be used to move file pointer at specific location before next read and write respectively. Methods tellg and tellp can be used to know the current file pointer position.

 

Additional References

 

 

1) Stanley Lippmann, “C++ Primer”, Pearson Education.

2) Bjarne Stroustrup, “The C++ Programming Language”, Pearson Education.

3) Scott Mayer, “Effective C++”, Addison Wesley.

4) Bhushan Trivedi , Programming with ANSI C++, 2/e , Oxford University Press.

5) Yashavant P. Kanetkar “Let Us C++” , Bpb Publications.

6) Abhiram G. Ranade, “An Introduction to Programming through C++” , McGraw Hill

7) Ellis and B. Stroustrup “Annotated C++ Reference Manual”, http://www.stroustrup.com/arm.html

8) Herbert Schildt, “Complete Reference C++”, McGraw Hill Publications.

9) Ashok Kamthane, “Object Oriented Programming with ANSI and Turbo C++”, Pearson Education

10) E Balaguruswami, “Object Oriented Programming With C++”, Tata McGraw Hill

11) “C++ FAQs”, Pearson Education.