
lafore_robert_objectoriented_programming_in_c
.pdf

606 |
Chapter 12 |
|
cout << “\nPerson “ << j; |
|
p.diskIn(j); |
//read person from disk |
p.showData(); |
//display person |
} |
|
cout << endl; |
|
return 0; |
|
} |
|
There shouldn’t be too many surprises here; you’ve seen most of the elements of this program before. It operates in the same way as the DISKFUN program. Notice, however, that all the details of disk operation are invisible to main(), having been hidden away in the person class.
We don’t know in advance where the data is that we’re going to read and write, since each object is in a different place in memory. However, the this pointer always tells us where we are when we’re in a member function. In the read() and write() stream functions, the memory address of the object to be read or written is *this and its size is sizeof(*this).
Here’s some output, assuming there were already two persons in the file when the program was started:
Enter data for person:
Enter name: Acheson
Enter age: 63
Enter another (y/n)? y
Enter data for person:
Enter name: Dulles
Enter age: 72
Enter another (y/n)? n
Person #1
Name: Stimson
Age: 45
Person #2
Name: Hull
Age: 58
Person #3
Name: Acheson
Age: 63
Person #4
Name: Dulles
Age: 72
If you want the user to be able to specify the filename used by the class, instead of hardwiring it into the member functions as we do here, you could create a static member variable (say char fileName[]) and a static function to set it. Or you might want to give each object the name of the file it was associated with, using a nonstatic function.


Chapter 12
608
No, because sizeof() isn’t a virtual function. It doesn’t know that it needs to consider the type of object pointed to, rather than the type of the pointer. It will always return the size of the base class object.
Using the typeid() Function
How can we find the size of an object, if all we have is a pointer to it? One answer to this is the typeid() function, introduced in Chapter 11. We can use this function to find the class of an object, and use this class name in sizeof(). To use typeid() you may need to enable a compiler option called Run-Time Type Information (RTTI). (This is the case in the current Microsoft compiler, as described in Appendix C, “Microsoft Visual C++.”)
Our next example shows how this works. Once we know the size of the object, we can use it in the write() function to write the object to disk.
We’ve added a simple user interface to the EMPLOY program, and made the member-specific functions virtual so we can use an array of pointers to objects. We’ve also incorporated some of the error-detection techniques discussed in the last section.
This is a rather ambitious program, but it demonstrates many of the techniques that could be used in a full-scale database application. It also shows the real power of OOP. How else could you use a single statement to write objects of different sizes to a file? Here’s the listing for
EMPL_IO:
//empl_io.cpp
//performs file I/O on employee objects
//handles different sized objects
#include <fstream> |
//for file-stream functions |
|
#include <iostream> |
|
|
#include <typeinfo> |
//for typeid() |
|
using namespace |
std; |
|
#include <process.h> |
//for exit() |
|
const int LEN = |
32; |
//maximum length of last names |
const int MAXEM |
= 100; |
//maximum number of employees |
enum employee_type {tmanager, |
tscientist, tlaborer}; |
////////////////////////////////////////////////////////////////
class employee |
//employee |
class |
{ |
|
|
private: |
|
|
char name[LEN]; |
//employee |
name |
unsigned long number; |
//employee |
number |
static int n; |
//current number of employees |
|
static employee* arrap[]; //array |
of ptrs to emps |
|
public: |
|
|


610 |
Chapter 12 |
|
private: |
|
int pubs; |
//number of publications |
public: |
|
void getdata() |
|
{ |
|
employee::getdata(); |
|
cout << “ Enter number of pubs: “; cin >> pubs; |
|
} |
|
void putdata() |
|
{ |
|
employee::putdata(); |
|
cout << “\n |
Number of publications: “ << pubs; |
} |
|
}; |
|
////////////////////////////////////////////////////////////////
//laborer class
class laborer : public employee
{
};
////////////////////////////////////////////////////////////////
//add employee to list in memory |
|
|
void employee::add() |
|
|
{ |
|
|
char ch; |
|
|
cout << “‘m’ to add a manager” |
|
|
“\n’s’ to add a scientist” |
|
|
“\n’l’ to add a laborer” |
|
|
“\nEnter selection: “; |
|
|
cin >> ch; |
|
|
switch(ch) |
|
|
{ |
//create specified employee type |
|
case ‘m’: arrap[n] = new manager; |
break; |
|
case ‘s’: arrap[n] = new scientist; break; |
||
case ‘l’: arrap[n] = new laborer; |
break; |
|
default: cout << “\nUnknown employee type\n”; return; |
||
} |
|
|
arrap[n++]->getdata(); |
//get employee data from user |
|
} |
|
|
//-------------------------------------------------------------- |
|
|
//display all employees |
|
|
void employee::display() |
|
|
{ |
|
|
for(int j=0; j<n; j++) |
|
|
{ |
|
|
cout << (j+1); |
//display number |
switch( arrap[j]->get_type() ) //display type


612 |
Chapter 12 |
|
} |
//write employee object to file |
ouf.write( (char*)(arrap[j]), size ); |
|
if(!ouf) |
|
{ cout << “\nCan’t write to file\n”; return; } |
|
} |
|
} |
|
//-------------------------------------------------------------- |
|
//read data for all employees |
from file into memory |
void employee::read() |
|
{ |
|
int size; |
//size of employee object |
employee_type etype; |
//type of employee |
ifstream inf; |
//open ifstream in binary |
inf.open(“EMPLOY.DAT”, ios::binary); |
|
if(!inf) |
|
{ cout << “\nCan’t open |
file\n”; return; } |
n = 0; |
//no employees in memory yet |
while(true) |
|
{ |
//read type of next employee |
inf.read( (char*)&etype, sizeof(etype) ); |
|
if( inf.eof() ) |
//quit loop on eof |
break; |
|
if(!inf) |
//error reading type |
{ cout << “\nCan’t read type from file\n”; return; } |
|
switch(etype) |
|
{ |
//make new employee |
case tmanager: |
//of correct type |
arrap[n] = new manager; size=sizeof(manager); break;
case tscientist:
arrap[n] = new scientist; size=sizeof(scientist); break;
case tlaborer:
arrap[n] = new laborer; size=sizeof(laborer); break;
default: cout << “\nUnknown type |
in file\n”; return; |
||
} |
//read data |
from file into it |
|
inf.read( (char*)arrap[n], size |
); |
|
|
if(!inf) |
//error |
but not eof |
|
{ cout << “\nCan’t read data |
from file\n”; return; } |
||
n++; |
//count |
employee |
}//end while
cout << “Reading “ << n << “ employees\n”;


Chapter 12
614
char someArray[MAX]; aClass* aPtr_to_Obj;
aPtr_to_Obj = reinterpret_cast<aClass*>(someArray); // don’t do this
However, this does not create an object, and attempts to use the pointer as if it pointed to an object will lead to trouble. There are only two legitimate ways to create an object. You can define it explicitly at compile time:
aClass anObj;
Or you can create it with new at runtime, and assign its location to a pointer:
aPtr_to_Obj = new aClass;
When you create an object properly, its constructor is invoked. This is necessary even if you have not defined a constructor and are using the default constructor. An object is more than an area of memory with data in it; it is also a set of member functions, some of which you don’t even see.
Interaction with EMPL_IO
Here’s some sample interaction with the program, in which we create a manager, a scientist, and a laborer in memory, write them to disk, read them back in, and display them. (For simplicity, multiword names and titles are not allowed; say VicePresident, not Vice President.)
‘a’ -- add data for an employee
‘d’ -- display data for all employees ‘w’ -- write all employee data to file ‘r’ -- read all employee data from file ‘x’ -- exit
Type selection: a ‘m’ to add a manager
‘s’ to add a scientist ‘l’ to add a laborer Type selection: m
Enter last name: Johnson Enter number: 1111
Enter title: President Enter golf club dues: 20000
‘a’ -- add data for an employee
‘d’ -- display data for all employees ‘w’ -- write all employee data to file ‘r’ -- read all employee data from file ‘x’ -- exit
Type selection: a ‘m’ to add a manager
‘s’ to add a scientist