Click to See Complete Forum and Search --> : How to write/read class to a binary file properly???


lost_scott
August 4th, 2006, 01:02 PM
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <fstream>
#include <vector>
#include "shortphone.cpp"
#include "shortname.cpp"
#include "shortaddress.cpp"
using namespace std;

class Record{
shortName Name;
shortAddress Address;
shortPhone Phone;
public:
void AddNewRecord();
void ViewRecord();
void InFile();
void OutFile();
int FindRecord();

};


int menu()
{
int choice;
cout <<" Record List Processing System "<<endl;
cout <<"1. Add New Record "<<endl;
cout <<"2. Remove A Record Object from the list"<<endl;
cout <<"3. View all record object"<<endl;
cout <<"4. Find the object"<<endl;
cout <<"5. Exit"<<endl;

cin >> choice;
return choice;
}

void Read_Str(string str)
{
ofstream fout("info.dat", ios::binary|ios::app);
string data;
data = str;
int tmp = data.size();
fout.write(reinterpret_cast<char *>(&tmp),sizeof(int));
fout.write(reinterpret_cast<char *>(&data), sizeof(tmp) );
fout.write(data.c_str(), tmp+1);
fout.close();
}






void Record::InFile()
{

Read_Str(Name.getFirst());
Read_Str(Name.getLast());

Read_Str(Address.getCity());
Read_Str(Address.getID());
Read_Str(Address.getState());
Read_Str(Address.getStreet());
Read_Str(Address.getZip());

Read_Str(Phone.getArea());
Read_Str(Phone.getNumber());
Read_Str(Phone.getPrefix());



}


void Record::AddNewRecord()
{
cin.ignore();

string input;

cout << "First Name :";
getline(cin,input,'\n');
Name.setFirst(input);

cout << "Last Name :";
getline(cin,input,'\n');
Name.setLast(input);

cout << "ID: :"; //Address
getline(cin,input,'\n');
Address.setID(input);

cout << "Street :";
getline(cin,input,'\n');
Address.setStreet(input);

cout << "City :";
getline(cin,input,'\n');
Address.setCity(input);

cout << "State :";
getline(cin,input,'\n');
Address.setState(input);

cout << "Zip :";
getline(cin,input,'\n');
Address.setZip(input);

cout << "Area Code :";
getline(cin,input,'\n');
Phone.setArea(input);

cout << "Telephone - Prefix :";
getline(cin,input,'\n');
Phone.setPrefix(input);

cout << "Telephone - Numbers:";
getline(cin,input,'\n');
Phone.setNumber(input);

input.erase();
}


void Record::ViewRecord()
{
cout << Name.getFirst() <<" " << Name.getLast() <<" ";
cout << Address.getID() <<" " << Address.getCity() <<" " << Address.getStreet() <<" " << Address.getState() <<" " << Address.getZip() <<setw(4) ;
cout << Phone.getArea() <<" " <<Phone.getPrefix() <<"-" << Phone.getNumber() <<endl;
}

int Record::FindRecord()
{
cin.ignore();

string input,last_name;
int i = 1;
last_name = Name.getLast();

cout <<"Enter the last name :";
getline(cin, input);

i=last_name.compare(input);
return i;
}

typedef vector<Record> SetRecord;

int main()
{
SetRecord GrowingRecord;

for(;;){
int choice = menu();
switch(choice)
{
case(1):
{
Record Employee;
Employee.AddNewRecord();

Employee.InFile();
GrowingRecord.push_back (Employee);

break;
}
case(2):
{
int i,j;

cout <<"Index " << " Name " << setw(2) << " ID " << setw(2) << " Street " << setw(5) << " City " << setw(5) <<"State "<<setw(2) << " Telephone " <<endl;
for(i=0; i<GrowingRecord.size();i++)
{
Record Employee;
Employee = GrowingRecord[i];
cout << i << setw(2);
Employee.ViewRecord();

}
if(i==0){
cout <<"No Records Exist! "<<endl;
break;
}
cout << "Which record do you wish to delete (-1 to exit) ? (0-" << i-1 <<")" ;
cin >> j;

GrowingRecord.erase(GrowingRecord.begin()+j);
}
break;

case(3):
{

cout <<"Index " << " Name " << setw(2) << " ID " << setw(2) << " Street " << setw(5) << " City " << setw(5) <<"State "<<setw(2) << " Telephone " <<endl;
for(int i=0; i<GrowingRecord.size();i++)
{
Record Employee;
Employee = GrowingRecord[i];
cout << i << " ";
Employee.ViewRecord();

}
Record Employee;

break;
}
case(4):
{
int j;
for(int i=0; i<GrowingRecord.size();i++)
{
Record Employee;
Employee = GrowingRecord[i];
if((j=Employee.FindRecord()==0)){
cout <<"Found !"<< "The data is located at " << i << "th data" <<endl; break;}
else
cout <<"Not Found !";
}
break;

}
case(5):
exit(1); break;

default:
cout <<"Error" << endl;
};
}
return 0;
}




here is the code...i got no problems when reading out the classes into a binary file....but it prints junk or nth when i read it back

can anyone figure out the problem??? many thanks

Calculator
August 4th, 2006, 01:36 PM
void Read_Str(string str)
{
ofstream fout("info.dat", ios::binary|ios::app);
string data;
data = str;
int tmp = data.size();
fout.write(reinterpret_cast<char *>(&tmp),sizeof(int));
fout.write(reinterpret_cast<char *>(&data), sizeof(tmp) );
fout.write(data.c_str(), tmp+1);
fout.close();
}

That code, especially

fout.write(reinterpret_cast<char *>(&data), sizeof(tmp) );

Is extremely wierd. First of all, you will not get good results writing an object that is not a POD (Plain Old Data) type in binary, as you do with that std::string. Secondly, you write only sizeof(tmp) bytes of the std::string to the file, only 4 bytes on a 32 bit machine.

Also funky about your code, is that you include other source files. This should not be necessary. You can have declarations of your classes and methods in header files, and include those in the source files that you need to, and then have other source files implement those methods. Generally good article: http://www.gamedev.net/reference/programming/features/orgfiles/

lost_scott
August 4th, 2006, 01:49 PM
void Read_Str(string str)
{
ofstream fout("info.dat", ios::binary|ios::app);
string data;
data = str;
int tmp = data.size();
fout.write(reinterpret_cast<char *>(&tmp),sizeof(int));
fout.write(reinterpret_cast<char *>(&data), sizeof(tmp) );
fout.write(data.c_str(), tmp+1);
fout.close();
}

That code, especially

fout.write(reinterpret_cast<char *>(&data), sizeof(tmp) );

Is extremely wierd. First of all, you will not get good results writing an object that is not a POD (Plain Old Data) type in binary, as you do with that std::string. Secondly, you write only sizeof(tmp) bytes of the std::string to the file, only 4 bytes on a 32 bit machine.

Also funky about your code, is that you include other source files. This should not be necessary. You can have declarations of your classes and methods in header files, and include those in the source files that you need to, and then have other source files implement those methods. Generally good article: http://www.gamedev.net/reference/programming/features/orgfiles/

thx for your reply...
actually these 3 extra headers are already written by my teacher....

can you tell me how to write the code correctly?

Calculator
August 4th, 2006, 02:04 PM
Well, as you have it, InFile outputs data because it uses Read_Str, which seems to write data. Little wierd, but okay? If your teacher told you to include those source files, you may show that article to him (if he/she is cool or something). I can only guess that OutFile, which reads in things (:)) is in one of those other source files, and I can't see how it is being read. But what you could do, is have length-prepended strings in your data file, or null-terminated strings (which do not know their own length) in the file. With length prepended, it would be like:

void Read_Str(string str)
{
ofstream fout("info.dat", ios::binary|ios::app);
int tmp = str.length();

fout.write(reinterpret_cast<char *>(&tmp),sizeof(tmp));
fout.write(data.c_str(), tmp);

// You wrote a length prepended string to the file.
// i.e. "Hello!" in the file would look like
// <binary data representing 6>Hello!

fout.close();
}

With null-terminated, it would be like:

void Read_Str(string str)
{
ofstream fout("info.dat", ios::binary|ios::app);

fout.write(data.c_str(), tmp + 1);

// You wrote a null-terminated c string
// i.e. "Hello!" in the file would look like
// Hello!\0

fout.close();
}

To read the strings from the file, you would for length prepended:

int length = 0;
inFile.read(reinterpret_cast<char*>(&length), sizeof length);
inFile.read(buffer, length);

string someString(buffer);

// Now some string is an std::string recreated from
// A length prepended string

Or for null terminated

string someString;
char tmp;
while((tmp = inFile.get()) != 0)
{
someString += tmp;
}

// Now someString was re-created from a
// null-terminated c string in the file

Or something.

lost_scott
August 5th, 2006, 01:27 AM
nice.....eventually i got the first list of data entered
however, it encountered other problems when i push it into a vector.

i copied the code to the main(), and i ran the following code

Record Employee;

for(int i=1;i<10;i++)
{

while(outFile.good())
{


int length =0;
outFile.read(reinterpret_cast<char*>(&length), sizeof length);
outFile.read(buffer, length);
string SomeBuffer(buffer);
cout << buffer << endl;

Employee.OutFile(SomeBuffer,i);

}
}

it displays junk after a complete string.....why??

TomWidmer
August 7th, 2006, 04:41 AM
outFile.read(buffer, length);
string SomeBuffer(buffer);
cout << buffer << endl;
it displays junk after a complete string.....why??You are constructing your string from a buffer that isn't null-terminated, yet you don't provide the length. You want: string SomeBuffer(buffer, length);

NMTop40
August 7th, 2006, 09:12 AM
There is no method to obtain a writable buffer to a string to write into, so to read back from the file you have to use vector<char> which does give you a writable buffer (of a variable size) and then copy the buffer.

Unfortunately this is also one of the inefficiencies of the standard library. firstly you create a vector of char which it has to initialise even though you don't need it initialised. Then after reading the contents in from a file (via yet another buffer that is used for the streambuf) you have to copy it into your string - string doesn't have a swap method with vector<char> - it can't because string isn't guaranteed to have a contiguous buffer.