Docsity
Docsity

Prepare for your exams
Prepare for your exams

Study with the several resources on Docsity


Earn points to download
Earn points to download

Earn points by helping other students or get them with a premium plan


Guidelines and tips
Guidelines and tips

Using File Streams in C++: Reading and Writing Text Files, Study notes of Computer Science

An overview of using file streams in C++ for reading and writing text files. It covers the basics of declaring file stream objects, opening and closing files, and processing input and output. The document also discusses error handling and some useful tips for working with file streams.

What you will learn

  • What are the steps for opening a file using a file stream object?
  • How do you handle errors when working with file streams in C++?
  • How do you declare file stream objects in C++?

Typology: Study notes

2019/2020

Uploaded on 10/26/2020

074-aditya-sharma
074-aditya-sharma 🇮🇳

2 documents

1 / 10

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Using C++ File Streams
David Kieras, EECS Dept., Univ. of Michigan
Revised for EECS 381, 9/20/2012
File streams are a lot like cin and cout
In Standard C++, you can do I/O to and from disk files very much like the ordinary console I/O streams cin and
cout. The object cin is a global object in the class istream (input stream), and the global object cout is a member
of the class ostream (output stream). File streams come in two flavors also: the class ifstream (input file stream)
inherits from istream, and the class ofstream (output file stream) inherits from ostream. Thus all of the member
functions and operators that you can apply to an istream or ostream object can also be applied to ifstream and
ofstream objects. However, file streams have some additional member functions and internal information reflecting
how they are connected to files on the disk. This document assumes the document Basic C++ Stream I/O.
This document is concerned only with the handiest form of disk file, called a text file – it contains a sequence of
ASCII characters, and is normally read or written from the beginning to the end. The stream-like nature of this is
obvious; a file stream is simply connected at one end to a disk file. In an input file stream, characters are moved from
the disk into the stream, and your program takes them out from the other end. For output, your program puts
characters into the stream, and the system takes them out of the other end and copies them onto the disk.
The major difference between file streams and the two console streams is when and how the stream objects are
created. The two console streams are created and set up for you when your program is started. The objects cin and
cout are global objects created outside your program. In contrast, you are responsible for creating and setting up
your own file streams; this is fine, since of course, you want to control which files are used for what purpose in your
program.
Basics of using file streams
Let's get a quick overview, and then get into some details.
First, you declare a file stream object for each file you need to simultaneously access. In this example, we will use
one input file, and output file. But your program can have and be using as many files simultaneously as you wish.
You just declare a stream object for each file:
#include <iostream>
#include <fstream>!// the class declarations for file stream objects
using namespace std;
...
int main ()
{
!ifstream my_input_file;!// an input file stream object
!ofstream my_output_file;!// an output file stream object
...
}
The above example code declares two objects, an input file stream object, and an output file stream object. Of
course, they can be named whatever you wish, like any other C++ variable.
1
pf3
pf4
pf5
pf8
pf9
pfa

Partial preview of the text

Download Using File Streams in C++: Reading and Writing Text Files and more Study notes Computer Science in PDF only on Docsity!

Using C++ File Streams

David Kieras, EECS Dept., Univ. of Michigan

Revised for EECS 381, 9/20/

File streams are a lot like cin and cout

In Standard C++, you can do I/O to and from disk files very much like the ordinary console I/O streams cin and

cout. The object cin is a global object in the class istream (input stream), and the global object cout is a member

of the class ostream (output stream). File streams come in two flavors also: the class ifstream (input file stream)

inherits from istream, and the class ofstream (output file stream) inherits from ostream. Thus all of the member

functions and operators that you can apply to an istream or ostream object can also be applied to ifstream and

ofstream objects. However, file streams have some additional member functions and internal information reflecting

how they are connected to files on the disk. This document assumes the document Basic C++ Stream I/O.

This document is concerned only with the handiest form of disk file, called a text file – it contains a sequence of

ASCII characters, and is normally read or written from the beginning to the end. The stream-like nature of this is

obvious; a file stream is simply connected at one end to a disk file. In an input file stream, characters are moved from

the disk into the stream, and your program takes them out from the other end. For output, your program puts

characters into the stream, and the system takes them out of the other end and copies them onto the disk.

The major difference between file streams and the two console streams is when and how the stream objects are

created. The two console streams are created and set up for you when your program is started. The objects cin and

cout are global objects created outside your program. In contrast, you are responsible for creating and setting up

your own file streams; this is fine, since of course, you want to control which files are used for what purpose in your

program.

Basics of using file streams

Let's get a quick overview, and then get into some details.

First, you declare a file stream object for each file you need to simultaneously access. In this example, we will use

one input file, and output file. But your program can have and be using as many files simultaneously as you wish.

You just declare a stream object for each file:

#include #include ! // the class declarations for file stream objects using namespace std; ... int main () { ! ifstream my_input_file;! // an input file stream object ! ofstream my_output_file;! // an output file stream object ... }

The above example code declares two objects, an input file stream object, and an output file stream object. Of

course, they can be named whatever you wish, like any other C++ variable.

A disk file consists of a body of text on the disk, arranged in a way determined by your computer's Operating System

(OS), which is responsible for keeping track of the information. If the file is deleted, moved, expanded, contracted,

etc., the OS keeps track of exactly where it is on the disk and how much of it there is. The C/C++ facilities for

working with disk files actually call OS subroutines to do the work.

So before you can use a disk file, you have to establish a relationship between your file stream object and the disk

file. More exactly, you have to ask the OS to connect your stream to the file. Fortunately, this is easy: Just tell the

stream object that you want to "open" the disk file and supply the name of the disk file as a C-string; the open

member function negotiates with the OS to locate that file on the disk and establish the connection between that file

and your stream object. Continuing the example:

! ifstream my_input_file;! // an input file stream object ! ofstream my_output_file;! // an output file stream object ! my_input_file.open("input_data");!// open the file named "input_data" ! my_output_file.open("output_data");! // open the file named "output_data"

Now the stream my_input_file is connected to the text file on disk named "input_data" and the stream

my_output_file is connected to the text file on disk named "output_data".

Instead of creating and then opening the file streams in separate statements, you can use a constructor that takes the

file name as an argument; after doing the normal initializations, the constructor completes the initialization by

opening the named file. The above four statements would then condense down to two:

! ifstream my_input_file("input_data");! // create and open ! ofstream my_output_file("output_data");

In both ways of opening a file, you can specify the file path or file name either with a C-string array or literal (as the

above examples do), or in C++11 with a std::string. For example:

! string filename; ! cin >> filename; ! ifstream my_input_file(filename);

When opening files, especially input files, is it critical to test for whether the open operation succeeded. File stream

errors are discussed in more detail below. But for now, here is one way of doing this test using a member function

that returns true if the file was successfully opened:

! if (my_input_file.is_open()) { !! // can continue, file opened correctly !! }

Now that the file streams are open, using them could not be simpler. We can read and write variable values from/to

the streams using the stream input and output operators just like with cin and cout. For example, to read an integer

and a double from the input file:

! my_input_file >> int_var >> double_var;!

To output to the file:

! my_output_file << "The integer is " << int_var << endl;

However, the situation is different with an output file. If you open a file for output, using only the normal default

specifications (as above) and the OS cannot find a file with that name, the open function creates a new empty file

with that name. Why? Because decades of experience shows this is the most convenient and sensible policy! This is

almost certainly what you want! This is why it is the default behavior.

But what if you open a file for output using only the normal default specifications (as above) and it already exists

and the OS finds it? The most sensible and convenient policy has proven to be the following: The existing file is

deleted, and a new empty file is created with the same name. Again, this is almost certainly what you want! This is

why it is the default behavior.

What if you want something different? Consult a reference for other member functions and opening options that you

can supply. Full flexibility is available, but it is idiomatic to use the defaults when they apply (which they usually

do).

Handy member functions for character and line input

In addition to using the stream input operator >>, there are member functions that you can use to input single

characters or long sequences of characters such as lines. These are actually inherited from the istream class, so they

can be used with either files or cin. Here are the handiest three, shown both as prototypes and as an example call:

int get(); cin.get();

Reads the next character, skipping nothing, and returns it as an integer. If eof is encountered, the function returns the

special value defined as the macro EOF. The need to test for EOF means that this form is relatively inconvenient for

reading from a file.

istream& get(char&); cin.get(char_variable);

Reads the next character, skipping nothing, into the supplied char variable (notice the reference parameter). The

returned value is a reference to the istream object; this returned value can be used to test the stream state.

istream& getline(char * array, int n); input_file.getline(buffer_char_array, buffer_length);

This function reads characters into the pointed-to character array, reading until it has either encountered a '\n' or

has read n-1 characters. It terminates the string of characters with '\0' so you get a valid C string regardless of how

much was read. As long as the supplied n is less than or equal to the array length, you will not overflow the array.

Conveniently, the '\n' character is removed from the stream and is not placed into the array, which is usually what

you want in order to read and process the information in a series of lines. The returned value is a reference to the

istream object, which can be used to test the stream state. If it fills the array without finding the '\n', the input

operation fails in the same way as invalid input (see below) to let you know that a newline was not found within the

size of the line you are trying to read.

If you want different behaviors from these, consult a reference for different forms of get and getline that allow

different possible terminators and different treatments of them. Note that if you want to read a line into a

std::string, there is a special function just for this purpose, declared in :

istream& getline(istream&, std::string&);

Because the string automatically expands as needed, reading a line into a std::string with this function cannot

overflow, and so is by far the best way to process a file (or cin input) a line at a time.

What can go wrong

A stream object has a set of internal bits as data members that "remember" the state of the stream. Normally the

stream is in the good state if it has been opened successfully and has not had a problem. An error turns on one of the

error bits, and then the stream is no longer good , and the bit stays on until it is cleared with the clear() member

function. Once an error bit is turned on, any I/O operation on the stream does nothing. So it is only valid to read

from or write to a stream that is in the good state. There are member functions for testing the stream state. The

names of these stream states, member functions, and bits are not very well chosen, so read the following carefully.

As you will see, bad is not the exact opposite of good , and there are multiple ways to fail , only one of which is

really bad.

If you try to open a file and it fails to open (e.g. the filename is wrong), the stream object goes into a not-good state;

if you want to try to open it again (e.g. with the right filename), you must first call the clear() member function to

reset the error bits.

While both input and output operations can produce errors, input errors are far more common and more important.

Thus the remainder of this document discusses only input error situations; but be aware that output errors exist.

There are three ways an input operation can produce an error:

1. The hardware or system software has a malfunction - this is a "hard" I/O error that your program typically can do

nothing about except try to muddle through (maybe it won't happen a second time) or quit. This type of error sets the

bad bit in the stream object, and the stream is no longer good. Fortunately, these are rare (unless you have flakey

hardware). In this course, we will not require testing for this possibility.

2. The input characters are not consistent with the input request, and as a result, the request can not be satisfied. This

can be due to errors in the input data (e.g. typing errors), or incorrect assumptions in how the program is written.

Such an error turns on the fail bit, and the stream is no longer good. This situation is called invalid input in this

document. The document on Basic C++ Stream I/O discusses invalid input and how to handle it in detail, so this

document will only describe the kinds of errors that appear with file streams.

3. In attempting to satisfy an input request, an end of file ( eof ) is encountered that prevents the request from being

satisfied. The eof bit will get set, and the stream is no longer good. Hitting the end of file means no more characters

are available from the source (usually a file, but there are platform-specific keystrokes that can be used to signal end-

of-file from the keyboard). Important: eof is not set when the last character in the file has been read, but only when

an attempt is made to read another character after the last one. The stream object will stay not-good until the bits are

cleared with the clear() member function. Note that closing the file will not clear the stream state, so if you read a

file to the end, and want to read it again with the same stream object, you should clear the stream state, then close

and re-open the file.

Often, when end-of-file is encountered, both the eof bit and the fail bit will both get set, because the attempt to

satisfy the input request failed because it could not find what was needed, and in the course of trying to find it, we

ran off the end of the file. However, in certain cases the end-of-file serves as a delimiter for numbers or strings, and

so it is possible to get an eof condition without a fail condition. In this course, we simplify matters by requiring that

all input text files have a newline character at the end of every line, including the last line. This means that all lines

terminate with a whitespace to serve as a delimiter for the last data item on a line, including the last data item on the

last line.

Now, getting an eof is not really an "error" unless there is supposed to be more input - checking for an eof condition

is a customary and standard way of determining that there is no more data to process. So there are two possible

The action your program takes on an input error depends on the type of error encountered. If it is invalid input, your

program needs to clean up the input and clear the stream, and attempt to continue. If it is an expected eof, the

program simply goes to the next step in the processing. But if the eof is unexpected, something is wrong, and the

user needs to be informed. Finally, if you are checking for hard I/O errors, you need to deal with it if it turns out to

be the problem.

A simple example

The following example program opens an input and output file, checks each one for being open, and then reads

integers from the input file and writes twice the value of each one to the output file. It continues until the input

stream is no longer good. This simple pattern would be justified if the programmer was confident that (1) the input

data was always valid (no garbage); (2) No hard I/O errors would occur; (3) If 1 and 2 turn out to be false, we can

recognize it by other means. Under these conditions, the stream input fails only at end of file, making for a very

simple file-reading loop. For some kinds of data files (e.g. string data) where invalid input cannot happen, this

approach is usually adequate. The pattern represented by this program is:

Attempt to read some input.

Check the stream state.

If the state is good , process the input.

If the state is not good , assume expected eof condition and continue processing.

#include

#include using namespace std; int main () { ! ifstream input_file("data.input");!! // open the input file ! if (!input_file.is_open()) {! // check for successful opening !! cout << "Input file could not be opened! Terminating!" << endl; !! return 1; !! } ! ofstream output_file("data.output");! // open the output file ! if (!output_file.is_open()) { // check for successful opening !! cout << "Output file could not be opened! Terminating!" << endl; !! return 1; !! } ! // read as long as the stream is good - any problem, just quit. ! // output is each number times two on a line by itself ! int datum; ! while (input_file >> datum) { !! output_file << datum * 2 << endl; !! }!! ! input_file.close(); ! output_file.close(); ! cout << "Done!" << endl; ! return 0; } --- input file --- 12 34 23 34 6 89 --- output file --- 24 68 46

bool get_int(istream& in_strm, bool& good_flag, int& x) {! ! bool continue_flag; ! ! in_strm >> x; ! if (in_strm.good()) { !! good_flag = true; !! continue_flag = true;! // can keep going !! } ! else if (in_strm.eof()) { !! cout << "End of file encountered." << endl; !! good_flag = false;!! // input value was not obtained !! continue_flag = false;! // time to stop !! } ! else if (in_strm.bad()) { !! cout << "Hard I/O error" << endl; !! good_flag = false; !! continue_flag = false;! // give up! !! } ! else if (in_strm.fail()) { !! cout << "Invalid input - skipping rest of line" << endl; !! in_strm.clear();! // don't forget! Must clear the stream to read it! !! char c; !! while (in_strm.get(c) && c != '\n'); // may hit eof while skipping !! good_flag = false;!! // value is not good !! if (in_strm.good())!! // did we hit eof or something else? !!! continue_flag = true;! // no - can keep going !! else { !!! continue_flag = false; // yes - time to stop !!! cout << "End of file or error while skipping rest of line." << endl; !!! } !! } ! else { !! cout << "Should be impossible to be here!" << endl; // for demo only! !! good_flag = false;! !! continue_flag = false; !! } ! return continue_flag; } --- input file --- 12

6t 89 x --- output --- value read is 12 value read is 34 Invalid input - skipping rest of line value read is 6 Invalid input - skipping rest of line value read is 89 Invalid input - skipping rest of line End of file or error while skipping rest of line. Done!