The second version of the Rolodex database program builds on and extends the first version. For brevity, previous descriptions are often not repeated here, making it imperative to fully understand rolodexdb (1) before continuing. This version adds an index file to speed access to the rolodex contact records stored in the data file. The examples focus on demonstrating the stream functions and using them to implement an ISAM system. Consequently, this implementation is only suitable for relatively small, simple databases - depending on the available memory, a few hundred thousand records. Nevertheless, it demonstrates the concepts underlying sophisticated database management systems. The final version of the Rolodex program consists of five files:
card.h
Unmodified from rolodexdb (1).
key.h
The key class specification and inline member functions. The program stores instances of this class in the index file.
rolodex.h
Adds features supporting the key class and index file.
rolodex.cpp
Adds and modifies the functions using the key class and index file.
rolodexdb.cpp
Unmodified from rolodexdb (1) example.
rolodexdb (2) classes and files.
The second version of the Rolodex example is similar to the first but adds a key class. Instances of the key class bind a contact's name to the absolute address of the corresponding contact record in the data file. The program maintains key objects or records in primary memory for fast access, which is impractical for all but the smallest databases. The text presents an alternate and more authentic approach later in this section. The program saves the keys in an index file, making them permanent and available for future use.
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.
The member fields for the name and data record position the key object binds together. See ISAM files and operations.
Constructors.
A getter function returning the position of a name.
The function sorts the keys alphabetically by name. Making it static allows other objects to easily sort an array of key objects.
Searches for name in the keys array.
An ordering function that alphabetically orders two key objects. The qsort and bsearch library functions call it, implying that programmers can't alter the argument and return types. Making the function a friend allows it to access the name and pos members.
class rolodex
{
private:
fstream index;
fstream data;
key keys[100]; // maintained in memory in sorted order
int rec_cnt = 0; // the number of rolodex and index records
public:
rolodex();
~rolodex();
void append(char* name, char* address, char* phone);
void search(char* name);
void list();
void edit(card contact);
void replace(int record, card contact);
private: // helper functions
void open(fstream& stream, char* file);
streampos write(card& c, streampos pos = -1);
card read(streampos pos);
};
The rolodex class (2).
The rolodex class, member function, and prototypes. The public functions, which form the public interface, remain unchanged from the first version, allowing the database program (rolodexdb) to remain unchanged. The second or ISAM version of the example adds an fstream member to access an index file. It maintains the keys in memory in sorted order throughout program operation. This organization requires two additional data members: an array of key objects or records and the number of database records. The private functions are not part of the public interface, but they implement reusable intra-class operations that avoid duplicate code. Please note the write function's default argument.
Reading Object-Oriented Code
Object-oriented programs consist of numerous objects, typically instantiated from a variety of classes, interacting and cooperating to solve a problem. Each class forms a unique scope, allowing programmers to use descriptive, meaningful function names without concern for name collisions caused by other classes defining functions with the same name. To understand object-oriented code, it's crucial to focus on how a program calls member functions. Specifically, focus on the object or class name to the left of the function call.
☐f()
this->f()
x.f()
x->f()
Bar::f()
(a)
(b)
(c)
(d)
(e)
No object implies f() is a member of the same class as the calling function.
Explicit alternate for (a).
x is an instance of a class defining the member function f().
x points to an instance of a class defining the member function f().
f() is a static member function defined in the Bar class.
rolodex::~rolodex()
{
index.seekp(0);
for (int i = 0; i < rec_cnt; i++)
index.write((char *) &keys[i], sizeof(key));
data.close();
index.close();
}
rolodex constructor and destructor.
The ISAM version of the rolodex constructor must open two files, each requiring the same file operations and validations. Accordingly, the example moves those operations to a private helper function. Furthermore, the constructor and destructor support maintaining a sorted array of key records in memory. The constructor reads and sorts the keys, and the destructor saves them and the data records before closing the files.
The append member function.
Creates and appends a new card record at the end of the data file. This operation also requires creating a new key record, counting it, inserting it into the keys array, and resorting the array.
Creates a new card with the argument data.
Calls the write function, which appends the new card at the end of the data file and returns its absolute file address.
Creates a new key with the provided information.
Adds the new key record to the keys and increments the key count.
Resorts the keys array.
void rolodex::search(char* name)
{
streampos pos = key::search(name, keys, rec_cnt); // (a)
if (pos >= 0) // (b)
read(pos).print(pos / sizeof(card));
else
cout << "Search name was not found in the Rolodex database.\n";
}
The search member function. Searches for name in the index file; if the function finds name, it prints the information from the corresponding card in the data file.
Searches for name in the keys array, returning the matching records' absolute address in the data file or -1 if it doesn't find name.
Prints the card data (the contact's name, address, and phone number) if the search finds a matching name or a failure message if it didn't.
void rolodex::list()
{
for (int r = 0; r < rec_cnt; r++) // (a)
read(keys[r].get_pos()).print(r); // (b)
}
The list member function.
The function lists each contact or Rolodex record in sorted order.
Iterates through the keys array.
Prints each Rolodex record on a separate line. Use operator associativity and precedence to determine the order of the sub-expression evaluation:
read(keys[r].get_pos()).print(r) extracts one key record from the keys array.
read(keys[r].get_pos()).print(r) returns the value saved in the key's pos field.
read(keys[r].get_pos()).print(r) reads the Rolodex record at address pos.
read(keys[r].get_pos()).print(r) calls the card print function to print the Rolodex contact.
void rolodex::edit(card contact)
{
streampos pos = key::search(contact.get_name(), keys, rec_cnt); // (a)
if (pos == (streampos)-1) // (b)
{
cout << contact.get_name() << " not found in the Rolodex\n";
data.clear();
return;
}
write(contact, pos); // (c)
}
The edit member function.
The function's parameter is an instance of card, containing a contact's name, new address, and phone number. The function changes the address and phone number of an existing Rolodex contact by searching for its name in the index file and replacing or overwriting it with the new information in the contact parameter.
Searches the keys array for the name field in contact.
key::search returns the absolute address of the matching Rolodex record, pos, or -1 if it doesn't find a match. If it fails to find a match, it prints a diagnostic message and clears or resets the error flags, allowing subsequent operations to continue.
Writes contact to the Rolodex data file, overwriting the existing record.
void rolodex::replace(int record, card contact)
{
if (record < 0 || record >= rec_cnt) // (a)
{
cout << "Block number out of bounds: 0 <= record < number of records\n";
return;
}
streampos pos = keys[record].get_pos(); // (b)
write(contact, pos); // (c)
keys[record] = key(contact.get_name(), pos); // (f)
key::sort(keys, rec_cnt); // (g)
}
The replace member function.
The function replaces the Rolodex record in the data file with record number record with the data in contact. It depends on the program maintaining the keys array in sorted order.
Ensures that the record number is valid.
Locates the key at record number in the keys array and gets the position of the corresponding Rolodex record in the data file.
Writes contact to the data file at position pos, overwriting the existing Rolodex record at that location.
Forces the stream to write pending data output to the data file.
Updates the key at record number in the keys array because the name may have changed.
Resorts the keys array, maintaining it in sorted order.
Converting a record number to an absolute address is fast and straightforward: record number × record size. Consequently, programmers can choose to store either record numbers or absolute addresses in key records.
The open member function.
The example generalizes the open function from the Rolodex (1) constructor to open the file named in the file parameter, allowing it to open both the data and index files. The function opens the file with and binds it to the stream parameter.
Attempts to open the file in binary mode for reading and writing. The open operation fails if the file does not already exist.
The is_open function returns true if the stream is open and false otherwise. The if-statement expression inverts or "flips" the result, detecting when the file didn't open properly.
Creates a new file using a temporary ofstream object.
Closes the new file, preparing to open it with the necessary modes.
Makes another attempt to open the data file in binary mode for reading and writing.
Aborts the program if the program fails to open the data or index file.
The rolodex write member function.
Writes a new card to the data file. Depending on the value of the pos argument, it either appends the card to the end of the file or overwrites an existing card. The if-test enables support for the append, edit, and replace operations. It depends on the default value of -1, established in the rolodex class specification, and allows the write function to leverage the stream positioning functions.
Move the "put" pointer to the end of the file to write a new card record.
Get the current "put" or write position in the data file.
Move the "put" pointer to pos, the location of an existing card record, to overwrite it.
Write the card.
Flush the buffer, writing all pending stream output to the file.
Return the position where the card record was written.