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;
}

 


Return to main page   Return to Tutorials