Reading with Input Streams - Reading and Writing Files - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book V

Reading and Writing Files

Chapter 3: Reading with Input Streams

In This Chapter

Reading with the extraction operators

Dealing with the end of the file

Reading various data types

Reading data that is formatted with text

Well, isn’t this nice. You have a file that you wrote to, but you need to read from it! After all, what good is a file if it’s just sitting on your hard drive collecting dust?

In this chapter, we show you how you can read from a file. Reading a file is tricky because you can run into some formatting issues. For example, you may have a line of text in a file with a sequence of 50 digits. Do those 50 digits correspond to 50 one-digit numbers, or maybe 25 two-digit numbers, or some other combination? If you created the file, you probably know; but the fun part is getting your C++ program to properly read from them. The file might contain 25 two-digit numbers, in which case you make sure that the C++ code doesn’t just try to read one enormous 50-digit number. In this chapter, we give you all the dirt on getting the dust off your hard drive and the file into memory. Have at it!

Extracting with Operators

When you read from a file, you can use the extraction operator, >>. This operator is very easy to use, provided you recognize that the phrase, “Look mom, no caveats!” just doesn’t apply to the extraction operator.

Suppose you have a file called Numbers.txt with the following text on one line:

100 50 30 25

You can easily read in these numbers with the following code. First, make sure you #include <fstream> (but not fstream.h, as you’ll pick up an old, outdated, yucky file). And you probably will need the line using namespace std; if you’re using a newer compiler and library. Then this code will do the work:

ifstream MyFile(“Numbers.txt”);

MyFile >> weight;

MyFile >> height;

MyFile >> width;

MyFile >> depth;

In the preceding code, the input file, Numbers.txt, had its numbers separated with spaces. You can also separate them with newlines, like this:

100

50

30

25

The program doesn’t care. It looks for white space, which is any number of spaces, tabs, and newlines. You could have the data like the following example, and the program will still read them in correctly.

100 50

30

25

image When you are dealing with the standard input object, cin, the same rules about white space apply: If you read in four numbers, like the following example, the cin object, like the ifstream object, will separate the numbers based on the white space.

cin >> weight;

cin >> height;

cin >> width;

cin >> depth;

image If the user accidentally inserts a space, the computer will apply the separated values in two places — both incorrectly. Be careful!

image When you are reading information from a file, make sure that you have clearly defined the order of the information. In other words, make sure that you have agreed upon a protocol for the information. Otherwise, you will likely end up with errors and mistakes, and your coworkers will want to blame somebody. That’s the way computer people are, after all.

What’s a protocol?

Okay, we’re going to give you a list of numbers: 1600 20500 1849 20240. Go ahead and use them the way we intend you to use them, and then send us back the answer, at which point we’ll send you our response. What’s that? You’re not sure what we want you to do with them? Aha! You need a protocol. As defined in this chapter, a protocol is simply a rule for how data is ordered. A protocol in general defines rules for exchanging information of any sort between computers (think of it as a diplomatic role).The two systems negotiate the exchange of data based on standardized rules.

As it happens, the first number in the set of numbers we provided is the street address of the White House in Washington, DC, and the second number is the zip code for the White House. The third number is the street address of the main office for the National Park Service headquarters, and the fourth is the National Park Service zip code. Of course, you probably didn’t realize that (unless you happen to work for the Park Service and recognized parts of your address!).

But now suppose you tell us, “Send me the White House street address, then its zip code, then the National Park Service street address, and then its zip code.” Then we would go ahead and send the four numbers to you. At that point, we wouldn’t have any need to send a bunch of extra information, such as English words describing what each number is. If we give them to you in the exact order you requested them, that will be all you need. In fact, if you’re writing a computer program that receives this information and we pad it with other information, such as descriptions, your program may not be equipped to handle all that info, and you have a problem. In other words, our program and your program must agree on a protocol. A protocol dictates the order of the information and how it’s formatted. Further, a protocol dictates how you’ll respond: You may send back a single number 1, which means that you received the data properly, and we may send a single 0, which means that you’ll be getting no further information. That’s a protocol, and protocols are useful when reading data, whether it’s from a file or over the Internet.

Encountering the End of File

When we get to the end of a really good novel, we often feel disappointed that it’s done, and we wish that we could just keep reading. But alas, we have encountered the EON (end of novel) condition.

Files have an ending as well, called the EOF, which stands for End of File. When you are reading from a file, you need to know when you reach the end. If you know how big the file is going to be, you can write your program so it knows exactly when to stop. So here are the cases we cover in this section: First, how you can read to the end of the file simply because you know how big the file is and, therefore, when to stop reading; and second, how you can keep reading until you reach the EOF without having to know the file size in advance.

You, the programmer, know the format of the file you are reading. (Perhaps your program even wrote the file and now you’re writing the part of the program that reads it.) And it’s possible your format starts with a size. For example, you may be reading in a file, and you start by reading a number from the file, where the number represents how many pieces of information you are to read from the file. This requires that whoever created the file started by writing the size before the rest of the data, and that you agree to this format.

Here’s an example. First, Listing 3-1 writes two files that you can later read in.

Listing 3-1: Using Code to Open a File and Write to It

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

void WriteFile(string filename, int count, int start)

{

ofstream outfile(filename.c_str());

outfile << count << endl;

int i;

for (i=0; i<count; i++)

{

outfile << start + i << endl;

}

outfile.close();

}

int main()

{

WriteFile(“/MyData/nums1.txt”, 5, 100);

WriteFile(“/MyData/nums2.txt”, 6, 200);

return 0;

}

You can see that this program writes two files. Create the \MyData folder before you run the application the first time. We gave a path in the files (/MyData/nums1.txt) to prevent problems with newer operating systems that don’t allow root directory access, such as Linux and Windows Vista. The reason we’re giving a path is that the next listing reads from the files, and we want to make sure that the program can find the files.

Note that the WriteFile function takes a filename, a count, and a start. It uses this information to write a series of numbers to the file. But before it writes those numbers, it writes the count, like this:

outfile << count << endl;

Then it uses a loop to write count numbers to the file.

And, of course, when the WriteFile function is all done, it closes the file. Good program, good.

Listing 3-2 is an example of how to read this data back in.

Listing 3-2: Using Code to Open a File and Read It Back In

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

void ReadFile(string filename)

{

ifstream infile(filename.c_str());

int count;

int i;

int num;

cout << “File: “ << filename << endl;

infile >> count;

cout << “This file has “ << count << “ items.” << endl;

for (i=0; i<count; i++)

{

infile >> num;

cout << num << endl;

}

infile.close();

}

int main()

{

ReadFile(“/MyData/nums1.txt”);

ReadFile(“/MyData/nums2.txt”);

return 0;

}

You can see in Listing 3-2, like Listing 3-1, filename includes the path names for the files the program is reading in.

Now look at the ReadFile function. This function opens the file and then immediately reads in a number. This number represents the number of items to read in. It’s the first number that was written by the WriteFile function in the previous listing, Listing 3-1.

image As the writer of both the program that writes the file and the program that reads the file, we agreed (both sides of us agreed, that is, or so we think) on the format of the file both for reading it and for writing it. And by sticking to this format, we can assure ourselves that we can read in the file that we previously wrote. When you run this program, you’ll see the following output.

File: /MyData/nums1.txt

This file has 5 items.

100

101

102

103

104

File: /MyData/nums2.txt

This file has 6 items.

200

201

202

203

204

205

Process returned 0 (0x0) execution time : 0.000 s

Press any key to continue.

Now another possibility for reading and writing a file is that you continue reading data from the file until you reach the end of the file. How do you do this? You test the istream or ifstream object for the EOF.

Listings 3-3 and 3-4 show you how you can do this. As with the earlier listings, the first writes a couple of files, and the second reads them in.

First, here’s Listing 3-3.

Listing 3-3: Using Code to Write to a File but Not Record a Count

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

void WriteFile(string filename, int count, int start)

{

ofstream outfile(filename.c_str());

int i;

for (i=0; i<count; i++)

{

outfile << start + i << endl;

}

outfile.close();

}

int main()

{

WriteFile(“/MyData/nums1.txt”, 5, 100);

WriteFile(“/MyData/nums2.txt”, 6, 200);

return 0;

}

As you can probably see, Listing 3-3 is like Listing 3-1, except Listing 3-3 does not start out by writing out a count.

Listing 3-4 reads the numbers back in.

Listing 3-4: Reading from a File and Looking for the EOF Condition

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

void ReadFile(string filename)

{

ifstream infile(filename.c_str());

int num;

cout << “File: “ << filename << endl;

bool done = false;

while (!done)

{

infile >> num;

if (infile.eof() == true)

{

done = true;

}

else

{

cout << num << endl;

}

}

infile.close();

}

int main()

{

ReadFile(“/MyData/nums1.txt”);

ReadFile(“/MyData/nums2.txt”);

return 0;

}

This listing is like Listing 3-2. However, instead of first reading a count, it just dives in and starts reading.

image Note carefully how Listing 3-4 does its thing: The listing first tries to read in a number, and then it checks if it encountered an EOF. If you’re familiar with other styles of reading in files, this approach may seem a little backwards to you. But that’s the way the streams do it: First read, and if the read doesn’t work, then abort.

Therefore, you have to have some strange logic in your code. Here’s the general algorithm:

set done to false

while not done

read a number

if encounter an end of file

set done to true

else

process the number read in

end-if

end-while

Something bothers us about this approach: We need a big if statement, and the process the number part goes in an else block. We like having a Boolean variable called done, because then we can use a while loop that reads like this:

while (!done) // pronounced “while not done”

If lots of processing is needed, the processing will all be piled inside the else portion of the if block. And that can get ugly with a million indentations. In this case, check for EOF and then break out of the loop, like this:

if (infile.eof() == true)

break;

But if you do that, you have no reason for the done variable. So what do you put in the while loop? A lot of people do this:

while (1)

{

infile >> num;

if (infile.eof() == true)

break;

cout << num << endl;

}

Yes, they put while (1). In other words, the while loop spins forever until a break statement comes along. We’re not particularly fond of the while (1) construct because it’s a bit counterintuitive for our little tiny brains, but a lot of people do it; and we have to admit that we like the short if statement, just breaking if the EOF is true. You choose which method you want to use. And heck, you may even be able to dream up a couple more ways to do this.

Reading Various Types

Reading a file is fun, but it can get complicated when you want to read spaces. Suppose we have these two strings that we want to write to a file:

“That’s the steak by the poodle that I’ll have for dinner.”

“I will have the Smiths for dinner, too.”

Now suppose you wrote these to a file as one big long line to get, “That’s the steak by the poodle that I’ll have for dinner. I will have the Smiths for dinner, too.”

Now later, you want to read back in these two strings. How can you do that? You can’t just do this:

string first, second;

infile >> first;

infile >> second;

If you do this, the variable first will hold That’s, and the variable second will hold the. Why? Because, when you read in strings, the ifstream and istream classes use spaces to break (or delimit) the strings. Bummer.

But even if you could somehow get the ifstream class to go past the spaces, how does the ifstream class know when it has reached the end of the first string? Now in this case, you may write your program to follow this protocol: A string ends with a period.

And that protocol is fine, because ending with a period is the case with these two strings that we wrote to the file. But what if they were just sequences of words, like this:

“poodle steak eat puddle”

“dinner Smiths yummy”

And then, when you write these two strings to a file, you may end up with this inside the file:

poodle steak eat puddle dinner Smiths yummy

Or worse, you may get this, which contains no space between the two strings:

poodle steak eat puddledinner Smiths yummy

imageHere’s what we suggest that you do: First, you must agree on a protocol. This, of course, may just mean agreeing with yourself. (Always a good idea. No, it’s not! Yes, it is!) Here are some choices for your protocols:

♦ You can write each string on a separate line, and when you read the file, you will know that each line is a separate string.

♦ You can delimit each string with a particular character. Then you would split your strings based on those delimiters.

Writing a string to a separate line is very easy; you simply do this:

cout << mystring << endl;

Nothing earth shattering there. Reading it in is pretty easy, too; you just use the getline function in the ifstream or istream class. The catch is that the getline function wants a character array, not a string object. So go ahead and read it into a character array, and then convert it to a string object like so:

char buf[1024];

infile.getline(&(buf[0]), 1024);

string str(buf);

imageYou don’t have to convert the array to a string object; if you want, you can just work with the character array. But we prefer to work with string objects because they’re instances of classes and you get all kinds of nice member functions that can manipulate the strings. We’re all about making our lives easier.

Listing 3-5 shows how to write the strings with a delimiter. You can see that this has nothing particularly magical about it (other than the quantum physics involved in running the microprocessor, but we don’t care about that).

Listing 3-5: Writing Strings with a Delimiter Is Easy

#include <iostream>

#include <string>

#include <fstream>

using namespace std;

void WriteString(ofstream &file, string words)

{

file << words;

file << “;”;

}

int main()

{

ofstream delimfile(“/MyData/delims.txt”);

WriteString(delimfile, “This is a dog”);

WriteString(delimfile, “Some dogs bite”);

WriteString(delimfile, “Some dogs don’t bite”);

WriteString(delimfile, “Humans that is”);

WriteString(delimfile, “All dogs bite”);

WriteString(delimfile, “Food that is”);

WriteString(delimfile, “I say, food food food.”);

delimfile.close();

return 0;

}

Listing 3-6 shows you how to read in the file strings. We used a trick for this. The ifstream class inherits from the istream class the getline function. Most people use this to read a line of text. However, a little-known fact is that you can specify the delimiter you prefer to use instead of the end-of-line character. You pass the delimiter as the third parameter. And so we passed a semicolon character for the third character, and lo and behold, the thing worked!

Listing 3-6: Specifying a Delimiter with the getline Function

#include <iostream>

#include <string>

#include <fstream>

using namespace std;

string ReadString(ifstream &file)

{

char buf[1024]; // Make sure this is big enough!

file.getline(&(buf[0]), 1024, ‘;’);

return string(buf);

}

int main()

{

ifstream delimfile(“/MyData/delims.txt”);

while (1)

{

string words = ReadString(delimfile);

if (delimfile.eof() == true)

break;

cout << words << endl;

}

delimfile.close();

return 0;

}

When you run Listing 3-6, you see this output:

This is a dog

Some dogs bite

Some dogs don’t bite

Humans that is

All dogs bite

Food that is

I say, food food food.

Yay! That’s the correct list of strings!

Reading Formatted Input

Sooner or later, you may be reading a file that has this kind of information in it:

Hello there my favorite number is 13. When I go to the

store I buy 52 items each week, except on dates that

start with 2, in which case I buy 53 items.

Hello there my favorite number is 18. When I go to the

store I buy 72 items each week, except on dates that

start with 1, in which case I buy 73 items.

Hello there my favorite number is 10. When I go to the

store I buy 40 items each week, except on dates that

start with 2, in which case I buy 41 items.

This file has a general format (or protocol!). How can you read in the numbers? One way is to read strings for each of the words and skip them. Here’s a sample piece of code that reads up to the first number, the favorite number:

ifstream infile(“words.txt”);

string skip;

for (int i=0; i<6; i++)

infile >> skip;

int favorite;

infile >> favorite;

This code reads in six strings and just ignores them. You can see how we do this through a loop that counts from 0 up to but not including 6. (Ah, you gotta love computers. Most people would just count 1 to 6. We suppose we could have, but we’re computer guys, no more, no less.)

Then, after we read the six strings that we just ignored, we finally read the favorite number. You can then repeat the same process to get the remaining numbers.