Basic File I/O - Streams
Why Files?
Note:
The method that we will discuss for doing file I/O will not allow programs to re-read or re-write data from a file. This means that we will be reading/writing from the beginning of the file and going as far as we need. There are other ways to do I/O but this will do for our purposes.
File I/O streams
In order to use a stream to get I/O from a file you must declare the stream and then connect the stream to the file.
e.g.
ifstream in_stream; ifstream infile;
ofstream out_stream; ofstream outfile;
You must also connect streams to a particular file and indicate the nature of the connection. Do you want to open or close the file.
To open a file, we use the function
open.in_stream.open("infile.dat");
out_stream.open("outfile.dat");
In general:
<stream name>.open(<file name>);Note:
Once declared and connected to a file, a file stream may be used in the same way as
cin, and cout.#include <fstream.h>
int main()
{
int num1, num2;
ifstream in_stream; // Declaring the input file stream
ofstream out_stream; // Declaring the output file stream
in_stream.open("input.dat"); // Opening streams to
out_stream.open("output.dat"); // respective files
out_stream << "Our first program using files!\n";
in_stream >> num1 >> num2;
out_stream << "The two numbers are "
<< num1 << " and " << num2 << endl;
in_stream.close(); // Closing
out_stream.close(); // streams
return 0;
}
Suppose the contents of
input.dat (opened in MSNotePad) are:1 2
The contents of
output.dat would be:Our first program using files!
The two numbers are 1 and 2
The contents of
input.dat could have appeared as:1 <cr>
2
or
1 2 3 9
Note that the program above ignores whitespace and only looks at the first two numbers.
Notice the two names for our files. One is the stream name that is connected to the file. The other is the external file name, that is, the physical name of the file on your disk. Once we connect to the physical file, we use the stream name
in_stream and not the physical file name input.dat.Files should be closed when no longer needed. In general:
<stream name>.close();
In our program example:
in_stream.close();
out_stream.close();
In general, C++ will close files when your program terminates, but it is good programming practice (and more reliable behavior) for you to explicitly do so.
Files connected to an input stream must exist or the opening of the file will fail. However, for Visual C++, if you open a file for input that does not exist, it will create it. This action may be suppressed by:
in_stream.open("infile.dat", ios::nocreate);
nocreate
means, do not to create the file if it does not exist.On the other hand, for all C++ implementations, if the output stream is connected to a file that does not exist, a new empty file with the supplied name will be created. If the file exists then the contents of the file will be discarded!! Ensure that input files exist and output files do not exist (or are files whose contents may be lost).
Checking that a file opened successfully
It is a good practice to check that both input and output files are opened correctly (the path to the file may have been specified incorrectly). Use the function
fail. (see pg 227 & 229)e.g:
in_stream.open("infile.dat", ios::nocreate);
if (in_stream.fail())
{
cout << "Input file opening failed. \n";
exit(1);
}
The
exit statement is a way of ending your program. The "1" indicates that an error occurred. Good programming style requires careful use of exit! For example, it should not be used in place of a return! Use of exit requires #include <stdlib.h>.
File I/O
Prompting for <filename>
To get the name of the file that you want to open from the user remember that a file name is just a string. We may therefore use an array of
char thus:char file_name[16];
Our file name in this case cannot have more than 15 characters. Remember to choose a number at least one larger than you expect to need (for
\0). For example: cout << "Enter the file name: ";
cin >> file_name;
Now we can use that name to open a file
ifstream in_stream;
in_stream.open(file_name, ios::nocreate);
<check for correct opening>
The
eof functionThe
eof function may be used to determine when an entire file has been read (eof means end of file). It requires no arguments and returns a Boolean value.e.g: Suppose we want to write all the characters from one file to another. Let
in_stream be the input stream and out_stream the output stream:in_stream >>.symbol; // first read a value
while (!in_stream.eof()) // then test if its EOF
{
out_stream << symbol; // if !EOF, output value
in_stream >>.symbol; // and read another value
}
Note
:Example:
Write a program that reads all the numbers in a file and copies them to an output file.
To do this we need to be able to read numbers until there are no more left. Assuming that input and output streams have been declared, the following code accomplishes the copy:
in_stream >> num;
while (!in_stream.eof())
{
out_stream << num << endl;
in_stream >> num;
}
The complete program:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main ()
{
ifstream in_stream;
ofstream out_stream;
int num;
char in_file[16], out_file[16];
cout << "This program will copy integers from one "
<< "file to another.\nPlease enter the input file name: ";
cin >> in_file;
in_stream.open(in_file, ios::nocreate);
if (in_stream.fail())
{
cout << "Error opening " << in_file << endl
<< "Exiting program ...\n";
exit(1);
}
cout << "Please enter the output file name: ";
cin >> out_file;
out_stream.open(out_file);
if (out_stream.fail())
{
cout << "Error opening " << out_file << endl
<< "Exiting program ... \n";
exit(1);
}
cout << "Beginning copying operation.\n";
in_stream >> num;
while (!in_stream.eof())
{
out_stream << num << endl;
in_stream >> num;
}
cout << "\nThe numbers have been copied from file " << in_file
<< " to file " << out_file << endl;
in_stream.close();
out_stream.close();
return 0;
}
Alternatively:
The extraction operator ">>" from an input file may be used in a logical expression. That is:
in_stream >> num
assigns the next number in our file to the variable
num (if there is one) and returns true if a number is available to be assigned (and false if no number is available). Hence, we may replace our looping structure above thus:while (in_stream >> num)
out_stream << num << endl;
I will use the
eof function approach in subsequent examples.Example:
Consider the following input file. Each line of the file starts with a batch number. Each batch consists of one or more detail lines. A batch will always contain an
a detail line followed by zero or more b detail lines. Two numeric values follow the identifier a or b on each line. Values are delimited by spaces.1240 a 501 0
1240 b 2 4
1240 b 1 3
1600 a 502 0
1600 b 22 99
1620 a 503 0
1650 a 504 0
1650 b 2 9
Write a program that copies this file to another file. Your program should filter out the
a detail line but it should retain the value following the a and use it in place of the b for b detail lines that are copied.The file would be transformed thus:
1240 501 2 4
1240 501 1 3
1600 502 22 99
1650 504 2 9
Solution:
Assuming input and output streams declared:
in_stream >> batch_no >> batch_type >> num1 >> num2;
while (!in_stream.eof())
{
tempa=num1;
in_stream >> batch_no >> batch_type
>> num1 >> num2;
while ((batch_type=='b') && !in_stream.eof())
{
out_stream << batch_no << " " << tempa << " "
<< num1 << " " << num2 << endl;
in_stream >> batch_no >> batch_type
>> num1 >> num2;
}
}
The complete program:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
int main ()
{
ifstream in_stream;
ofstream out_stream;
char in_file[16], out_file[16];
cout << "This program will copy integers from one "
<< "file to another.\nPlease enter the input file name: ";
cin >> in_file;
in_stream.open(in_file, ios::nocreate);
if (in_stream.fail())
{
cout << "Error opening " << in_file << endl
<< "Exiting program ...\n";
exit(1);
}
cout << "Please enter the output file name: ";
cin >> out_file;
out_stream.open(out_file);
if (out_stream.fail())
{
cout << "Error opening " << out_file << endl
<< "Exiting program ... \n";
exit(1);
}
cout << "Beginning copying operation.\n";
char batch_type;
int batch_no, num1, num2, tempa;
in_stream >> batch_no >> batch_type >> num1 >> num2;
while (!in_stream.eof())
{
tempa=num1;
in_stream >> batch_no >> batch_type >> num1 >> num2;
while ((batch_type=='b') && !in_stream.eof())
{
out_stream << batch_no << " " << tempa << " "
<< num1 << " " << num2 << endl;
in_stream >> batch_no >> batch_type >> num1 >> num2;
}
}
cout << "\nThe numbers have been copied from file " << in_file
<< " to file " << out_file << endl;
in_stream.close();
out_stream.close();
return 0;
}
Aside - Streams as Arguments to functions
Streams are like variables, and as such may be passed as arguments to functions. However, they must be call-by-reference arguments:
void my_function (ofstream& out_file)
{
out_file << "This is my function.\n"
<< "It doesn't do much.\n";
return;
}
#include <iostream.h>
Quick Introduction to Classes and Objects
We will discuss the object-oriented paradigm in more detail later. For now, we will discuss two of the fundamental constructs that object-orientation is based on. That is, classes and objects.
You often see the phrase: "A class is a type whose variables are objects."
Classes and Objects
The "richer" structure and behavior of objects comes about because classes consist of function definitions (known as member functions of the class) and data types.
Classes may be pre-defined or user-defined. We will focus initially on pre-defined classes.
Classes and Objects - Streams
Streams are implemented in C++ as classes. For example,
ifstream and ofstream are classes. Hence, the following declarations, define objects:ifstream in_stream; ifstream infile;
ofstream out_stream; ofstream outfile;
The objects are
in_stream, infile, out_stream, and outfile.We have seen several examples of member functions that are associated with these classes. For example,
ifstream has member functions open and close (as well as others). Similarly ofstream.In order to call a member function, you need to specify the object.
in_stream.open("infile.dat");
in_stream.close();
The dot before the member function name is called the dot operator and the object named before the dot is called the calling object. Hence,
in_stream is the calling object for the member functions open and close in the example above.
Member functions for Stream I/O
Remember the "magic formula" (to format decimal places):
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout.precision(2);
setf
and precision are member functions that may be used with the object cout.These member functions are also defined for file streams. That is, for any declared output file:
out_stream.setf(ios::fixed);
out_stream.setf(ios::showpoint);
out_stream.precision(2);
Decimal numbers output to the file associated with
out_stream will have two digits after the decimal point. The number of places printed after the decimal point is determined by the member function precision.Notice that the
precision call only applies to the file associated with out_stream. Any other output stream would have to have precision defined separately.setf
means "set flag" and when called, tells the output stream to output to the stream in a certain way that depends on the flag provided.See page 237 for useful output flags. Note that to unset a flag you need to call
unsetf with the flag that was set.e.g.:
out_stream.unsetf(ios::showpoint);Character I/O
All of the I/O that we do is in terms of characters. If the user types a number in, it must first be turned into a series of characters from the keyboard and then interpreted as a number by the extraction operator (>>).
e.g: The user types 203 but the computer sees '2' '0' '3' and it is the I/O operators that do the conversion for us to the number 203.
Sometimes we want to directly manipulate the characters ourselves and so bypass the automatic conversion that is done by the extraction operator.
e.g: Let us say we need to account for whitespaces typed by a user!
Member functions
get and putEvery input stream has a member function called
get. The get function allows your program to read one character of input and store it in a variable of type char. char next_symbol;
cin.get(next_symbol);
Note: Blanks and newline characters are valid characters and will not be skipped.
In-class problem
Suppose we have an input file:
Aa
Bb
Cc
that is connected to the input stream
in_stream. char c1,c2,c3,c4;
in_stream.get(c1);
in_stream.get(c2);
in_stream.get(c3);
in_stream.get(c4);
What values will
c1,c2,c3,c4 have?Member functions
get and putWe may use this to allow us to read until the end of a line. The following echoes a line of input that the user enters.
cout << "Enter a line of input to be echoed: ";
do
{
cin.get(symbol);
cout << symbol;
} while (symbol != '\n');
cout << "Isn't that clever?\n";
Output streams have a corresponding member function
put, which allows you to output one character to the output stream. cin.get or cin.put need #include <iostream.h> and file I/O streams need #include <fstream.h>.Unexpected '
\n' in the inputA common mistake when doing character I/O is forgetting to discard the newline character at the end of each line.
In the problem above, if we really wanted the four variables to contain the first four characters, we must be careful of the newline character. Make sure that you read up to and including the newline character when you read a line of input.
Predefined character functions
Consider a program to read a text file and replace capital letters with lowercase letters and vice versa. Built-in character functions may be used to achieve this (
#include <ctype.h>).The following are also useful (see pages 868-869):
Note:
These functions are defined to return
int instead of char. If assigned to a variable of type char then conversion is automatic:e.g:
char c;
c = tolower('Z');
cout << "c is: " << c << endl;
will output
c is: z. If we try to output it directly then we will get into trouble because it's actually an int.e.g:
cout << tolower('Z') << endl;will output
90 to the screen. To get around that we have to cast to a character.e.g:
cout << char (tolower('Z')) << endl;will print
zProgram to convert uppercase to lowercase:
#include<iostream.h>
#include<fstream.h>
#include<ctype.h>
void get_input_file( ifstream& file);
void get_output_file( ofstream& file);
void upperToLower (ifstream& in_stream, ofstream& out_stream);
// Copy characters from in_stream to out_stream,
// changing the upper case letters into lower case letters
int main()
{
ifstream in_stream;
ofstream out_stream;
cout << "This program copies a file and converts all\n"
<< "upper case character to lower case.\n\n";
get_input_file(in_stream);
get_output_file(out_stream);
upperToLower(in_stream,out_stream);
in_stream.close();
out_stream.close();
return 0;
}
void upperToLower(ifstream& in_stream, ofstream& out_stream)
{
char symbol;
cout << "Converting...\n";
in_stream.get(symbol);
while (! in_stream.eof())
{
if (isupper(symbol))
cout << "'" << symbol << "' will be converted.\n";
out_stream << char(tolower(symbol));
in_stream.get(symbol);
}
cout << "Done.\n\n";
}
void get_input_file(ifstream& file)
{
char name[20];
cout << "Input file: "; cin >> name;
file.open(name, ios::nocreate);
while (file.fail() )
{
cout << "The file '" << name << "' cannot be opened.\n";
cout << "\nInput file: ";
cin >> name;
file.clear();
file.open(name, ios::nocreate);
}
}
void get_output_file( ofstream& file)
{
char name[20];
cout << "Output file: "; cin >> name;
file.open(name);
while (file.fail() )
{
cout << "The file '" << name << "' cannot be opened.\n";
cout << "\nOutput file: ";
cin >> name;
file.clear();
file.open(name);
}
}
When do we use "
>>" vs. get?The only time that you want to use
get in your programs is when you care about every single character in the filee.g: You want to write a text editor. You would use
get since spaces matter. If you want to input numbers or strings as we have been all along and you don't care about spaces, keep using the extraction operator >>.Consider the following program:
#include<iostream.h>
#include<fstream.h>
#include<ctype.h>
void get_input_file( ifstream& file);
void get_output_file( ofstream& file);
void format(ifstream infile, ofstream ofile);
// Copies infile to otfile, converting \n -> ' ', ';' -> \n and digits -> '#'
int main()
{
ifstream in_stream;
ofstream out_stream;
cout << "This program copies a file into the following format:\n"
<< "\n\t- converts all newline characters to spaces\n"
<< "\t- converts all semicolons to newlines\n"
<< "\t- converts all digits to '#'\n\n";
get_input_file(in_stream);
get_output_file(out_stream);
format(in_stream,out_stream);
in_stream.close();
out_stream.close();
return 0;
}
void format(ifstream infile, ofstream ofile)
{
char symbol;
cout << "Formatting...\n";
infile.get(symbol);
while (! infile.eof())
{
if (symbol == '\n')
symbol = ' ';
else if (symbol == ';')
symbol = '\n';
else if ( isdigit(symbol) )
symbol = '#';
ofile << symbol;
infile.get(symbol);
cout << ".";
}
cout << "\nDone.\n\n";
return;
}
void get_input_file(ifstream& file)
{
char name[20];
cout << "Input file: ";
cin >> name;
file.open(name, ios::nocreate);
while (file.fail() )
{
cout << "The file '" << name << "' cannot be opened.\n";
cout << "\nInput file: ";
cin >> name;
file.clear();
file.open(name, ios::nocreate);
}
}
void get_output_file( ofstream& file)
{
char name[20];
cout << "Output file: ";
cin >> name;
file.open(name);
while (file.fail() )
{
cout << "The file '" << name << "' cannot be opened.\n";
cout << "\nOutput file: ";
cin >> name;
file.clear();
file.open(name);
}