14.11.1. rolodexdb.cpp(2): ISAM Example

Review

The final version of the Rolodex program utilizes concepts and C library functions introduced previously. Please review the following as needed:

The next version of the Rolodex database program adds an index file to speed access to the rolodex records stored in the data file. As before, we focus on demonstrating the C++ stream member functions that enable the described functionality rather than on producing product-ready code. As such, we'll rely on a binary search, implemented with library functions, to look up the keys in the index file. The final version of the Rolodex program consists of five files:

card.h
Unmodified from the rolodexdb version 1 example: the card class specification and member functions.
key.h
The key class specification and inline member functions.
rolodex.h
The rolodex class specification and member function prototypes.
rolodex.cpp
Contains the rolodex class member function definitions.
rolodexdb.cpp
Unmodified from the rolodexdb version 1 example: defines main and other application functions.

The following figures describe the main features of the Rolodex database program. The source code for the complete program is available at the bottom of the page.

Feature Description
char		name[NAME_SZ];
streampos	pos;
Private member fields for the name and data record position fields.
key()
Constructor that builds an empty key.
key(char* n)
Constructor that builds a key with just the name field filled - used to create a key object for use with the bsearch library function.
key(char* n, streampos pos)
Constructor that build a completely filled key object for mapping a name to a position the data file.
streampos get_pos()
A getter function that returns the position value.
static void sort(key* keys, int count)
A function that sorts the keys alphabetically by name. The function is made static - i.e., a class function - because it operates on an array of key objects rather than on a single, target object.
friend int korder(const void* e1, const void* e2)
A function that alphabetically orders two key objects. The function's signature (i.e., the argument list and return type) cannot be changed because the function is used by two C library functions, qsort and bsearch, but the function must access private members of the key class. Making the function a friend of the key class solves the conflict.
key.h. Instances of the key class, stored in the index file, map names to rolodex records in the data file.

 

Feature Description
fstream index;
fstream data;
Stream objects to access the index and data files; both are private data members.
rolodex();
Constructor that creates and opens the index and data files.
~rolodex() { index.close(); data.close(); }
Destructor that closes the index and data files.
void add(char* name, char* address, char* phone);
Adds a new card at the end of the data file: Figure 3.
void search(char* name);
Searches for name in the index file; if the name is found, prints the information from the data file: Figure 4.
void list();
Lists all entries in the data file in alphabetical order Figure 5.
void edit(char* name, char* address, char* phone);
Edits or updates an existing card in the data file: Figure 6.
private/helper functions
void open(fstream& stream, char* file);
Opens the index and data files: Figure 7.
key* find(char* name, key* keys);
Finds name in the index file and returns a pointer to the key: Figure 8.
int load_keys(key* keys);
Loads all the keys from the index file and sorts them alphabetically by name: Figure 9.
streampos write(card& c, streampos pos = -1);
Writes a card to the data file; if pos is -1, the card is appended at the end of the file, otherwise it is written at position pos: Figure 10.
void append(char* name, streampos pos);
Appends a key at the end of the index file: Figure 11.
card read(streampos pos);
Reads and returns a card from the data file: Figure 12.
rolodex.h. The rolodex class and member function prototypes.

 

void rolodex::add(char* name, char* address, char* phone)
{
	card	c(name, address, phone);		// (a)
	streampos pos = write(c);			// (b)
	append(name, pos);				// (c)
}
The add member function. Creates new card and key records and appends them to the data and key files.
  1. Creates a new card with the argument data.
  2. The write function writes the card to the end of the data file and returns its position.
  3. Creates and appends a new key record to the end of the index file. The key maps name to the card's position in the data file.

 

 

void rolodex::list()
{
	key	keys[100];				// (a)
	int	count = load_keys(keys);		// (b)
	card	c;

	for (int i = 0; i < count; i++)			// (c)
		read(keys[i].get_pos()).print();	// (d)

	/*card	c;
	data.seekg(0);
	while (data.read((char *) &c, sizeof(card)))
		c.print();
	data.clear();*/
}
The list member function. Two different list orders are possible. The first order uses the sorted keys to print the list in alphabetical order. The second, commented out and identical to the previous version, prints the cards in insertion order.
  1. An array to hold all key objects in the index file.
  2. Loads the keys into the array and sorts them in alphabetical order. Returns the number of keys loaded into the array.
  3. Does the following for each key in the array:
  4. Gets the position from the key, reads the card at that position in the data file, and prints the card information to the console.

 

void rolodex::edit(char* name, char* address, char* phone)
{
	key	keys[100];					// (a)
	key*	match = find(name, keys);			// (b)

	if (match != nullptr)					// (c)
	{
		streampos pos = match->get_pos();		// (d)
		cout << "Old: ";
		read(pos).print();				// (e)

		card	c(name, address, phone);		// (f)
		cout << "New: ";
		c.print();					// (g)
		write(c, pos);					// (h)
	}
	else
		cout << "Search name not found\n";
}
The edit member function. Searches for name in the index file. It reads the record if one is found, updates the record, and saves the updated record over the old one.
  1. An array to hold all key objects in the index file.
  2. Loads the keys into the array and searches for name in the keys. Returns nullptr if a match is not found.
  3. Prints either the current card information or a failure message.
  4. Gets the position from the key.
  5. Reads the card at that position in the data file, and prints the card information to the console.
  6. Creates a new card with the argument list information.
  7. Prints the new card information to the console.
  8. Replaces (i.e., overwrites) the existing card in the data file with the new card.

 

void rolodex::open(fstream& stream, char* file)
{
	stream.open(file, ios::binary | ios::in | ios::out);		// (a)
	if (!stream.is_open())						// (b)
	{
		ofstream make(file);					// (c)
		make.close();						// (d)
		stream.open(file, ios::binary | ios::in | ios::out);	// (e)
	}

	if (!stream.good())						// (f)
	{
		cerr << "Unable to open " << file << endl;
		exit(1);
	}
}
The open member function. The open function is adapted from the constructor appearing in the previous version of the rolodex class. Previously, there was only one file to open; this version has two files and the steps needed to open the files are the same.
  1. Attempts to open the file in binary mode for reading and writing - this operation fails if the file does not already exist.
  2. Tests the file to see if it opened - will be false if the file doesn't exist.
  3. Creates a new file using a temporary ofstream object.
  4. Closes the new file.
  5. Makes another attempt to open the data file in binary mode for reading and writing.
  6. Aborts the program if the data file cannot be created or opened.

 

key* rolodex::find(char* name, key* keys)
{
	int	count = load_keys(keys);				// (a)

	key sk(name);							// (b)
	return (key *)bsearch(&sk, keys, count, sizeof(key), korder);	// (c)
}
The rolodex find member function. Finds a key by name in an array of keys. Returns a pointer to the key if found.
  1. Loads the keys into the array and sorts them in alphabetical order. Returns the number of keys loaded into the array.
  2. Creates a key object with the search name to be used with the qsort and bsearch library functions.
  3. Returns a pointer to the key object that matches the search name.

 

int rolodex::load_keys(key* keys)
{
	int	count = 0;						// (a)

	index.seekg(0);							// (b)
	while (index.read((char *) &keys[count], sizeof(key)))		// (c)
		count++;
	key::sort(keys, count);						// (d)
	index.clear();							// (e)

	return count;							// (f)
}
The rolodex load_keys member function. Loads all of the keys in the index file into an array and sorts them alphabetically by name.
  1. An accumulator to count how many keys are in the index file.
  2. Move the get or read pointer to the beginning of the index file.
  3. Read each key into one element of the the array and increment the count.
  4. Sort the array of keys.
  5. Clear the end of file flag.
  6. Return the number of keys in the array.

 

streampos rolodex::write(card& c, streampos pos)
{
	if (pos == (streampos) -1)				// (a)
		data.seekp(0, ios::end);			// (b)
	else
		data.seekp(pos);				// (c)
	pos = data.tellp();					// (d)
	data.write((char *) &c, sizeof(card));			// (f)
	data.flush();						// (g)
	return pos;						// (h)
}
The rolodex write member function. Writes a new card to the data file. Depending on the value of the pos argument, either appends the card to the end of the file or overwrites an existing card.
  1. The variable pos is set to -1 by a default argument but the value can be overridden in the function call with the position in the data file of an existing card.
  2. Move the put pointer to the end of the file to write a new card.
  3. Move the put pointer to the position of an existing card to overwrite the card as part of an edit operation.
  4. Get the current put or write position in the data file.
  5. Write the card.
  6. Flush the buffer. Data written to a file is saved in a buffer and only written to the file when the buffer is full. The stream flush function flushes the buffer (i.e., forces the write operation to run) so that the new or updated record is available for the next operation.
  7. Return the position where the card was written.

 

void rolodex::append(char* name, streampos pos)
{
	key	k(name, pos);				// (a)
	index.seekp(0, ios::end);			// (b)
	index.write((char *) &k, sizeof(key));		// (c)
	index.flush();					// (d)
}
The rolodex append member function. Appends a new key at the end of the index file.
  1. Creates a new key object.
  2. Moves the put pointer to the end of the index file.
  3. Writes the key to the index file.
  4. Flushes the index file.

 

card rolodex::read(streampos pos)
{
	card	c;					// (a)
	data.seekg(pos);				// (b)
	data.read((char *) &c, sizeof(card));		// (c)
	return c;					// (d)
}
The rolodex read member function. Reads a card at the specified position from the data file.
  1. An empty card to hold the data read from the data file.
  2. Move the get pointer to the specified position in the data file.
  3. Read a card from the data file.
  4. Return the card.

Downloadable Files