Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

lafore_robert_objectoriented_programming_in_c

.pdf
Скачиваний:
51
Добавлен:
27.03.2023
Размер:
8.94 Mб
Скачать

Streams and Files

605

void diskOut();

//write to file

static int diskCount();

//return number of

 

// persons in file

};

 

//--------------------------------------------------------------

 

void person::diskIn(int pn)

//read person number pn

{

//from file

ifstream infile;

//make stream

infile.open(“PERSFILE.DAT”, ios::binary);

//open

it

infile.seekg( pn*sizeof(person) );

//move

file ptr

infile.read( (char*)this, sizeof(*this) ); //read one person

}

 

//--------------------------------------------------------------

 

void person::diskOut()

//write person to end of file

{

 

ofstream outfile;

//make stream

 

//open it

outfile.open(“PERSFILE.DAT”, ios::app | ios::binary);

outfile.write( (char*)this, sizeof(*this) ); //write to it

}

 

//--------------------------------------------------------------

 

int person::diskCount()

//return number of persons

{

//in file

ifstream infile;

 

infile.open(“PERSFILE.DAT”, ios::binary);

infile.seekg(0, ios::end);

//go to 0 bytes from end

//calculate number of persons return (int)infile.tellg() / sizeof(person);

}

////////////////////////////////////////////////////////////////

int main()

 

{

 

person p;

//make an empty person

char ch;

 

do {

//save persons to disk

cout << “Enter data for person:”;

p.getData();

//get data

p.diskOut();

//write to disk

cout << “Do another (y/n)? “;

cin >> ch;

 

} while(ch==’y’);

//until user enters ‘n’

int n = person::diskCount(); //how many persons in file? cout << “There are “ << n << “ persons in file\n”; for(int j=0; j<n; j++) //for each one,

{

12

AND S

F TREAMS

ILES

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.

Streams and Files

607

Classes That Read and Write Themselves

Let’s assume you have many objects in memory, and you want to write them all to a file. It’s not efficient to have a member function for each object open the file, write one object to it, and then close it, as in the REWOBJ example. It’s much faster—and the more objects there are the truer this is—to open the file once, write all the objects to it, and then close it.

Static Functions

One way to write many objects at once is to use a static member function, which applies to the class as a whole rather than to each object. This function can write all the objects at once. How will such a function know where all the objects are? It can access an array of pointers to the objects, which can be stored as static data. As each object is created, a pointer to it is stored in this array. A static data member also keeps track of how many objects have been created. The static write() function can open the file; then in a loop go through the array, writing each object in turn; and finally close the file.

Size of Derived Objects

To make things really interesting, let’s make a further assumption: that the objects stored in memory are different sizes. Why would this be true? This situation typically arises when several classes are derived from a base class. For example, consider the EMPLOY program in Chapter 9, “Inheritance.” Here we have an employee class that acts as a base class for the manager, scientist, and laborer classes. Objects of these three derived classes are different sizes, since they contain different amounts of data. Specifically, in addition to the name and employee number, which apply to all employees, the manager has a title and golf club dues and the scientist has a number of publications.

We would like to write the data from a list containing all three types of derived objects (manager, scientist, and laborer) using a simple loop and the write() member function of ofstream. But to use this function we need to know how large the object is, since that’s its second argument.

Suppose we have an array of pointers (call it arrap[]) to objects of type employee. These pointers can point to objects of the three derived classes. (See the VIRTPERS program in Chapter 11 for an example of an array of pointers to objects of derived classes.) We know that if we’re using virtual functions we can make statements like

arrap[j]->putdata();

The version of the putdata() function that matches the object pointed to by the pointer will be used, rather than the function in the base class. But can we also use the sizeof() function to return the size of a pointer argument? That is, can we say

ouf.write( (char*)arrap[j], sizeof(*arrap[j]) ); // no good

12

AND S

F TREAMS

ILES

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:

 

 

Streams and Files

609

virtual void getdata()

{

cin.ignore(10, ‘\n’);

cout << “ Enter last name: “; cin >> name;

cout << “ Enter number: “;

cin >> number;

}

 

 

 

virtual void putdata()

 

 

{

 

 

 

cout << “\n

Name: “ << name;

 

cout << “\n

Number: “ << number;

}

 

 

 

virtual employee_type get_type();

//get type

static void add();

//add an employee

static void display();

//display all employees

static void read();

//read from disk file

static void write();

//write to disk file

};

 

 

 

//--------------------------------------------------------------

 

 

 

//static variables

 

 

 

int employee::n;

 

//current number of employees

employee* employee::arrap[MAXEM]; //array of ptrs to emps

////////////////////////////////////////////////////////////////

//manager class

 

class manager : public employee

{

 

 

private:

 

 

char

title[LEN];

//”vice-president” etc.

double dues;

//golf club dues

public:

 

 

void

getdata()

 

{

 

 

employee::getdata();

 

cout << “

Enter title: “;

cin >> title;

cout << “ Enter golf club dues: “; cin >> dues;

}

 

 

void putdata()

 

 

{

 

 

employee::putdata();

 

cout << “\n

Title: “ << title;

 

cout << “\n

Golf club dues: “ << dues;

}

 

 

};

////////////////////////////////////////////////////////////////

//scientist class

class scientist : public employee

{

12

AND S

F TREAMS

ILES

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

Streams and Files

611

{

 

 

 

case

tmanager:

cout << “. Type: Manager”;

break;

case

tscientist:

cout << “. Type: Scientist”;

break;

case

tlaborer:

cout << “. Type: Laborer”;

break;

default: cout << “. Unknown type”;

 

}

 

 

 

arrap[j]->putdata();

//display employee data

 

cout <<

endl;

 

 

}

 

 

 

}

 

 

 

//--------------------------------------------------------------

 

 

 

//return the type of this object

 

employee_type

employee::get_type()

 

{

if( typeid(*this) == typeid(manager) ) return tmanager;

else if( typeid(*this)==typeid(scientist) ) return tscientist;

else if( typeid(*this)==typeid(laborer) ) return tlaborer;

else

{ cerr << “\nBad employee type”; exit(1); } return tmanager;

}

//--------------------------------------------------------------

//write all current memory objects to file void employee::write()

{

int size;

cout << “Writing “ << n << “ employees.\n”;

ofstream ouf;

//open

ofstream in binary

employee_type etype;

//type

of each employee object

ouf.open(“EMPLOY.DAT”, ios::trunc | ios::binary); if(!ouf)

{ cout << “\nCan’t open file\n”; return; }

for(int j=0; j<n; j++)

//for every employee object

{

 

//get its type

etype =

arrap[j]->get_type();

 

 

//write type to file

ouf.write( (char*)&etype, sizeof(etype) );

switch(etype)

//find its size

{

 

 

case

tmanager:

size=sizeof(manager); break;

case

tscientist:

size=sizeof(scientist); break;

case

tlaborer:

size=sizeof(laborer); break;

12

AND S

F TREAMS

ILES

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”;

Streams and Files

613

}

////////////////////////////////////////////////////////////////

int main()

 

 

{

 

 

char ch;

 

 

while(true)

 

 

{

 

 

cout <<

“‘a’ -- add data for an employee”

 

“\n’d’ -- display data for all employees”

 

“\n’w’ -- write all employee data to file”

 

“\n’r’ -- read all employee data from file”

 

“\n’x’ -- exit”

 

 

“\nEnter selection: “;

cin >> ch;

 

switch(ch)

 

{

 

 

case

‘a’:

//add an employee to list

employee::add(); break;

case

‘d’:

//display all employees

employee::display(); break;

case

‘w’:

//write employees to file

employee::write(); break;

case

‘r’:

//read all employees from file

employee::read(); break;

case

‘x’: exit(0);

//exit program

default: cout << “\nUnknown command”;

}//end switch

}//end while return 0;

}//end main()

12

AND S

F TREAMS

ILES

Code Number for Object Type

We know how to find the class of an object that’s in memory, but how do we know the class of the object whose data we’re about to read from the disk? There’s no magic function to help us with this one. When we write an object’s data to disk, we need to write a code number (the enum variable employee_type) directly to the disk just before the object’s data. Then, when we are about to read an object back from the file to memory, we read this value and create a new object of the type indicated. Finally, we copy the data from the file into this new object.

No Homemade Objects, Please

Incidentally, you might be tempted to read an object’s data into just anyplace, say into an array of type char, and then set a pointer-to-object to point to this area, perhaps with a cast to make it kosher.

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