Text File I/O
File I/O allows applications
to access (store or retrieve) persistent information. With normal
applications that use variables to store information in memory, we lose
any information after the application is finished. If we turn off
the machine that is running the application, all the information stored
in memory will be lost (it will be reinitialized the next time that we
turn on the machine).
With file IO, we can store
information on the hard disk or floppy; that information will not
be lost when the application finishes or when the machine is rebooted.
When we want to use file IO
in our application(s), we need to decide:
1 - Whether we want to store
information in the disk or to read information from the disk.
2 - Where in the disk do we
want the information (if we are storing) or where do we have it (if we
are reading from it)
Writing
to files:
In the first example, I will
write a small demo program to write the name of a user and a password
to a file called passwords.txt
#include <iostream>
#include <fstream> //
necessary when using file IO
#include <string>
using namespace std;
int main()
{
string user_name, password;
cout
<< "Enter user name (can't have spaces): ";
cin >> user_name;
cout
<< "Choose a password (can't have spaces): ";
cin >> password;
// Now that we have all the information, we store it in the file
ofstream
pwd_file ("passwords.txt");
//
This creates a variable names pwd_file that is attached to the file
//
passwords.txt on the disk. Now, we can store information in that
//
file using the variable associated to it (pwd_file)
pwd_file
<< user_name << ' ' << password << endl;
return
0;
}
Notice that sending information
to a file is very similar as sending information to the screen.
Instead of cout, you use the variable associated to the
file.
This program has one minor
flaw (actually, it's not that minor!).
For instance, suppose that
you run ths program on a diskette that is write protected. Or on
a hard disk that is full, or on a network drive for which you don't have
privileged access. In any of these cases, the line in which we open
the file (ofstream pwd_file("passwords.txt")) creates the file if it doesn't
exist. However, any attempt to create a file given any of the above
conditions will fail! After that, the program is simply assuming
that the file was succesfully opened (created) and that we can go ahead
and store information in it.
An improved version of that
simple program would be the following, where we include validation for
the attempt to open the file, and print an error message in case it failed:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string user_name, password;
cout
<< "Enter user name (can't have spaces): ";
cin >> user_name;
cout
<< "Choose a password (can't have spaces): ";
cin >> password;
ofstream
pwd_file ("passwords.txt");
if
(pwd_file)
{
pwd_file << user_name
<< ' ' << password << endl;
}
else
{
cout << "Error
attempting to open file `passwords.txt'" << endl;
}
return
0;
}
The variable pwd_file
can be used as a condition, and it evaluates to true or false depending
on whether or not the attempt to open the file succeeded or not.
Notice that alternatively, we could have done the following:
if (! pwd_file)
{
cout << "Error attempting to open file
`passwords.txt'" << endl;
exit(1); //
quit the program
}
pwd_file << user_name
....
.....
In this case, we are using
the ! operator (not) to check if the file open went wrong. If it
did (go wrong), we exit (that is, we quit the program immediately);
if not, then the execution continues normally.
If you tried that program,
then you should have now a file called `passwords.txt' on the same directory
where you ran the application. If you are using Linux/Unix, you
could use the cat command to display its contents (cat is a unix command
used to display the contents of a text file). If you type this command
at the Linux prompt:
[... web1]$
cat passwords.txt
Then you would see the line
of text displayed on the screen.
Also, you could use the Text
Editor (the same editor you use to edit your C++ files) and do File/Open
and select the file `passwords.txt'. In either case, you will see
the contents of that file according to what you just sent to it.
If you are using Windows, then
you could use the type command (type in Windows is similar to the cat
command in Linux) from a DOS command prompt:
C:\...>
type passwords.txt
Or you could use the Notepad
and do File/Open and select your file. Again, in either case you
will see the contents of the file, according to what your program stored
in it.
Appending
information to files:
Another inconvenience of that
program is that each time that we execute it, the existing data is overwritten
(remember that when you open a file for output -- i.e., as an ofstream
variable -- if the file does not exist, it is created, and if it does
exist, it is destroyed and created again). In a situation like this,
what the program should do is add one line with a new user/password
each time it is executed (as opposed to replace the one line in the file
with the new one).
To do that, the file should
be opened in append mode. That is, we should setup the access to
the file passwords.txt specifying that we are going to append information
to the contents that is already there. If the file indicated already
exists, then all the information that we store in it will be stored after
the information that was originally in the file. If it doesn't exist,
then it is created and information is output to it normally.
The previous example can be
modified to append to the file as follows:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string user_name, password;
cout
<< "Enter user name (can't have spaces): ";
cin >> user_name;
cout
<< "Choose a password (can't have spaces): ";
cin >> password;
fstream
pwd_file ("passwords.txt", ios::app);
if
(pwd_file)
{
pwd_file << user_name
<< ' ' << password << endl;
}
else
{
cout << "Error
attempting to open file `passwords.txt'" << endl;
}
return
0;
}
Notice that the only difference
is in the line where we open the file. Instead of ofstream,
we use fstream, and we must indicate a second parameter,
ios::app.
However, notice that we still
must be careful and check if we could succesfully open the file
(remember, nobody can guarantee that the disk will not be full or write-protected,
or that we have privileged access to the file or to the directory or the
network drive where we are trying to open the file).
Reading
information from files:
The previous examples showed
how to write information to files (creating them and updating them).
Other applications may need to read the information to the file(s) and
do something with it. For example, in the case of the users and
passwords, we may have an application that creates the passwords file
and allows users to register (thus, updating the contents of the file),
and then another application would need to read the information from that
file to verify if a user is registered, or to verify the password and
decide if the user should be authorized or not.
To read information from a
file, we need to open the file in input mode. This can be done by
declaring the variable attached to the file as an ifstream
variable (instead of ofstream or fstream).
In the example below, one line of text is read from the file passwords.txt:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string line;
ifstream pwd_file ("passwords.txt");
if
(pwd_file)
{
getline (pwd_file, line);
cout << "Line
read from file: `" << line << "'" << endl;
}
else
{
cout << "Error:
either the file `passwords.txt' doesn't exist"
<< " or you are not authorized to access it" << endl;
}
return
0;
}
This simple program will read
one line from the file and will display it.
Of course, we could have read
the user and password into two separate variables, instead of reading
the whole line into a single string variable:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string user_name, password;
ifstream
pwd_file ("passwords.txt");
if
(pwd_file)
{
pwd_file >> user_name
>> password;
cout << "User
name: " << user_name << endl
<< "Password: " << password << endl;
}
else
{
cout << "Error:
either the file `passwords.txt' doesn't exist"
<< " or you are not authorized to access it" << endl;
}
return
0;
}
That is, we use getline if
we want to read one entire line (without stopping at spaces), or we use
the >> operator (as we did with cin) if we want to read
individual pieces separated by spaces (or tabs, or newlines).
Both versions of this example
have an important inconvenience: they only read the first line of
the file! In a real-life example, we would want to check if a user
is registered, or validate a user name/password pair to grant or refuse
access, etc. In that case, we would need to read all of the lines
of text that the file contains. That can be done by placing the
read statement in a loop. Notice that it has to be a while loop,
and not a for loop. We don't know how many lines the file contains,
which means that we shouldn't use a for loop.
The following example reads
and displays all the registered users and their passwords, as per the
passwords.txt file:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string user_name, password;
ifstream
pwd_file ("passwords.txt");
if
(! pwd_file)
{
cout << "Error:
either the file `passwords.txt' doesn't exist"
<< " or you are not authorized to access it" << endl;
exit(1);
}
// print header
cout << "Registered users and passwords:"
<< endl << endl;
// now loop reading and printing every user and its password
while (pwd_file >> user_name >> password)
{
cout << "User
name: " << user_name << endl
<< "Password: " << password << endl;
}
return
0;
}
Notice that the same instruction
that reads from the file acts as the condition to continue! It will
return a boolean value indicating if the read operation was successful.
If it was, we continue in the loop. If it wasn't, that means that
the end of the file was reached, and there are no more text lines;
thus, we stop the loop and the execution of the program finishes.
Now, what if we wanted to display
only the user names? You have to be careful about the following
detail: your program would still have to read both user name and
password, except that it will only display the user name. This has
to be done like that because the file contains the passwords, that you
want to display it or not! The example below shows this:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string user_name, password;
ifstream
pwd_file ("passwords.txt");
if
(! pwd_file)
{
cout << "Error:
either the file `passwords.txt' doesn't exist"
<< " or you are not authorized to access it" << endl;
exit(1);
}
cout
<< "Registered users:" << endl << endl;
// now loop reading user and password, and printing only user name
while (pwd_file >> user_name >> password)
{
cout << "User
name: " << user_name << endl;
}
return
0;
}
Thus, if the file contains
the following lines:
carlos
carlos_pwd
john pwd_johnny
Then the first time, carlos
will be read into the variable user_name and carlos_pwd
into the variable password. Only carlos will be
displayed, as the program only prints user_name, and not
password. The second time, john will be read into
the variable user_name, and pwd_johnny into the variable
password.
Notice that if we mistakenly
write the following:
while
(pwd_file >> user_name)
{
cout << "User name: " << user_name << endl;
}
The output of the program would
be something like:
User name:
carlos
User name: carlos_pwd
User name: john
User name: pwd_johnny
Since each pass of the loop
we only read one piece (one word, or token) into the variable user_name.
The next pass of the loop, we will read the next word in the file
(the password, then the user name in the next line, then password, and
so on).
In this final example, the
program asks the user for their user name and verifies if the user is
registered (only checks if the user is registered, without validating
the password or asking the user for it):
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string user_name, password;
string search_user;
cout
<< "Enter your user name (no spaces): ";
cin >> search_user;
ifstream
pwd_file ("passwords.txt");
if
(! pwd_file)
{
cout << "Error:
either the file `passwords.txt' doesn't exist"
<< " or you are not authorized to access it" << endl;
exit(1);
}
bool
user_registered = false;
while
(pwd_file >> user_name >> password && !user_registered)
{
if (user_name == search_user)
{
user_registered = true;
}
}
if
(user_registered)
{
cout << "Hello
" << search_user << ". You are one of our registered
users." << endl;
}
else
{
cout << "Sorry
" << search_user << ". You are not one of our registered users."
<< endl;
}
return
0;
}
|