14.6.2. s-rolodex.cpp: string Class I/O Example

Review

The first Rolodex problem solution is based on the C++ string class. It's generally easier to write and test programs incrementally rather than wait until we finish to test them. Developing software with frequent write-test cycles makes it easier to localize new problems: they are likely in the most recently added code, meaning there are fewer places for bugs to hide, narrowing our search for them. Once we verify a logical group of statements, we can more confidently rely on them.

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
using namespace std;


int main()
{
	ifstream in("rolodex.txt");

	if (!in.good())
	{
		cerr << "Unable to open \"rolodex.txt\"\n";
		exit(1);
	}

	return 0;
}
s-rolodex.cpp (step 1). Opening a file and ensuring it opened correctly is the first step to processing its contents. The initial part of the example program opens the file with an input stream constructor and tests the stream's error flags with the good function. Validating the error detection code is crucial, so we pause program development to conduct the first test. Although this code is incomplete and doesn't solve the overall Rolodex problem, it is complete enough to compile and run. Running the code before creating the rolodex.txt allows us to test the if-statement.

The validation test, based on the good function, is simple and generally effective. However, it fails to detect an easily overlooked condition because, technically, it isn't an error. If a file exists but is empty, the open operation doesn't set any error flags or the eofbit, but it does set the goodbit to 1.

Programs often subject file input to complex operations, and if a program exhibits a problem, it's easy to suspect those operations. From personal experience, I know how frustrating it is to focus on the processing operations only to discover that the failure results from the comparatively simple file-read operation. Therefore, I recommend validating the file-read code before processing the file's contents.

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
using namespace std;


int main()
{
	ifstream in("rolodex.txt");

	if (!in.good())
	{
		cerr << "Unable to open \"rolodex.txt\"\n";
		exit(1);
	}

	string	line;

	while (getline(in, line))
		cout << line << endl;

	return 0;
}
s-rolodex.cpp (step 2). A simple while-loop reads each line of text from the file and echoes it to the console, validating the read-loop syntax and demonstrating the basic getline function's behavior. If the file exists but is empty, the program passes the validation test and attempts to read the file. Embedding the getline function call in the while-loop detects the end-of-file condition before "processing" the empty string.

At this time, create the rolodex.txt file and compile and run the program. If the program's output is identical to the input file's contents, continue to the final step; otherwise, look for a logical error in the read loop before proceeding. The highlighted code is NOT part of the final program and must be removed or commented out of the final program.

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
using namespace std;


int main()
{
	ifstream in("rolodex.txt");

	if (!in.good())
	{
		cerr << "Unable to open \"rolodex.txt\"\n";
		exit(1);
	}

	/*string	line;

	while (getline(in, line))
		cout << line << endl;*/

	while (!in.eof())
	{
		string	name;				// (a)
		getline(in, name, ':');			// (b)

		string	address;			// (a)
		getline(in, address, ':');		// (b)


		string	phone;				// (a)
		getline(in, phone, '\n');		// (b)
		//getline(in, phone);			// (c)

		cout << left << setw(20) << name <<
			setw(35) << address <<
			setw(20) << phone << endl;
	}

	return 0;
}
s-rolodex.cpp (final). The final, complete version opens the file and validates that it opened correctly. It reads the fields in each line one at a time, saving each field in a separate string variable. The "processing" consists of formatting and printing each field. The test loop is commented out but remains in the code for possible future use.
  1. string variables to save the three fields as the program extracts them from the file.
  2. The program uses the three-parameter version of the getline function to extract the fields from each line of the input file. The first two getline function calls specify the colon character as the delimiter, while the last call specifies the newline character. If the third call uses the colon for the delimiter, the read "wraps around" to the next line, including its "name" field with the preceding line's phone number.
  3. Alternatively, the program can use the two-parameter version for the last getline call, which stops after reading and discarding the newline at the end of each line in the file.
This version of the program has two problems: First, if a line does not conform to the expected pattern, the program will "get lost," incorrectly processing the errant and subsequent lines. Second, if the file exists but is empty, a special case of not conforming to the expected pattern, the program will iterate once without input. We'll harden the program, correcting both problems, when we revisit bulletproof data entry at the end of the chapter.

Downloadable File

ViewDownloadComments
s-rolodex.cpp s-rolodex.cpp An example program reading and parsing fields from a line-oriented data file, saving them in strings for processing.