We continue to use a Rolodex to demonstrate I/O operations, but we replace the line-oriented operations of the previous examples with block I/O functions. Where past Rolodex examples used a line of text to represent a record or a Rolodex card (i.e., all the information about a person), the following example uses a structure named card. Instances of the card structure replace the delimiter-separated fields for name, address, and phone number with three separate structure members.
const int NAME_SZ = 20; // (a) const int ADDRESS_SZ = 35; const int PHONE_SZ = 20; struct card // (b) { char name[NAME_SZ]; char address[ADDRESS_SZ]; char phone[PHONE_SZ]; };
card
structure represents one record or entry in the Rolodex file - that is, the information for one person. The most notable feature of the card structure is that the fields are C-strings, which are much easier to use with the block I/O functions than are instances of the string
class. In a "real" program, the declarations, including the structure specification, would be placed in a header file and #included in all source code (i.e., .cpp) files.
The program's goal is the illustration of the block I/O functions and necessary supporting code, so the examples are unrealistically simplified. Furthermore, the code is grouped into functional units to facilitate the presentation; the complete program is available at the bottom of the page. To provide some context, especially the definition of the file streams, we begin with a partial main
function.
#include <iostream> // (a) #include <fstream> #include <iomanip> #include <cstring> //#include card constants and structure using namespace std; int main() { ofstream out("rolodex", ios::binary); // (b) if (!out.good()) // (c) { cerr << "Unable to open \"rolodex\"\n"; exit(1); } // code to define variables and write the data // (d) out.flush(); // (e) out.close(); ifstream in("rolodex", ios::binary); // (f) if (!in.good()) // (g) { cerr << "Unable to open \"rolodex\"\n"; exit(1); } // code to define variables and read the data // (h) return 0; }
flush
and close
functions force the operating system to finish the output operations before the program attempts to read the objects.Each card or record entered into the file is an instance of the card
structure. So, the file representing the rolodex looks very much like Figure 2(a) where each square is an instance of card
. Three different options for writing the objects to the file are described below.
card c0 = { "Bill Gates", "1 Microsoft Way, Redmond, WA", "(403) 123-4567" }; // (a) card c1 = { "Cranston Snort", "1600 Pennsylvania Ave", "(306) 678-9876" }; card c2 = { "Albert Einstein", "Princeton, NJ", "(456) 123-8765" }; card c3 = { "John Smith", "123 Elm St.", "801-555-1234" }; out.write((char *) &c0, sizeof(card)); // (b) out.write((char *) &c1, sizeof(card)); out.write((char *) &c2, sizeof(card)); out.write((char *) &c3, sizeof(card));
write
option 1: using the block write
function to output one structure object to a file. A "real" program would likely define a single structure object, fill it with data (perhaps as a user enters it), and use a single write
function call to output the object to a file. Such a code pattern would be appropriately placed in an application function or in a loop. Here, multiple variables and function calls demonstrate how objects are written sequentially to a file.
sizeof
operator finds the size, measured in bytes, of the structure object, which becomes the second parameter for the write
function callcard deck[] = // (a) { { "Bill Gates", "1 Microsoft Way, Redmond, WA", "(403) 123-4567" }, { "Cranston Snort", "1600 Pennsylvania Ave", "(306) 678-9876" }, { "Albert Einstein", "Princeton, NJ", "(456) 123-8765" }, { "John Smith", "123 Elm St.", "801-555-1234" } }; for (int i = 0; i < sizeof(deck) / sizeof(card); i++) // (b) out.write((char *) &deck[i], sizeof(card));
write
option 2: writing an array to a file. Some problems call for an array of objects. For example, arrays may be reorganized, sorted, and searched. When all processing is finished, arrays are easily written to a file with a for-loop.
sizeof(deck) / sizeof(card)
would likely be replaced with a variable storing the number of objects in the array - that is, the number of array elements that are actually used.card deck[] = // (a) { { "Bill Gates", "1 Microsoft Way, Redmond, WA", "(403) 123-4567" }, { "Cranston Snort", "1600 Pennsylvania Ave", "(306) 678-9876" }, { "Albert Einstein", "Princeton, NJ", "(456) 123-8765" }, { "John Smith", "123 Elm St.", "801-555-1234" } }; out.write((char *) deck, 4 * sizeof(card)); // (b)
write
option 3: writing arrays to a file with a single write
function call. Writing a whole array to a file is very easy.
write
function can take advantage of this organization and write the entire array as one block. Note that the value "4" in the calculation of the second parameter is replaced by the number of elements in the array.card c; // (a) while (in.read((char *) &c, sizeof(card))) // (b) { cout << left << setw(NAME_SZ) << c.name; cout << setw(ADDRESS_SZ) << c.address; cout << setw(PHONE_SZ) << c.phone << endl; }
read
option 1: reading multiple objects one at a time. We can easily process a file containing structure objects with a while-loop. We see the code pattern illustrated here in many file processing problems. In this example, once the program reads an object, only display it on the console; replace the output statements to provide the processing needed for a specific problem.
sizeof
operator finds the number of bytes in the structure, which forms the second parameterread
function reads one object from the file and stores it in the structure variableread
function returns a reference to in
and the overloaded conversion operator, operator bool();
converts it into a Boolean value that drives the loopcard array[4]; // (a) in.read((char *) array, 4 * sizeof(card)); // (b) for (int i = 0; i < 4; i++) // (c) { cout << left << setw(NAME_SZ) << array[i].name; cout << setw(ADDRESS_SZ) << array[i].address; cout << setw(PHONE_SZ) << array[i].phone << endl; }
read
option 2: reading multiple objects with a single function call. The code pattern illustrated here is much less common than the previous one because it requires programmers to know how many objects the file contains before defining the array and calling the read
function. However, if the number of objects is known in advance, this is a convenient and compact way of quickly filling an array of objects with data read from a file.
card
structure objects.sizeof
operator provides the number of bytes in a single card
object, which is multiplied by the number of objects that the read
function reads from the file.