c++ this to write object into binary file - c++

I've some trouble with this pointer in c++ function.
I would write my class into binary file, so I write this function member
void Product::saveProducts(){
fstream file;
Product temp;
temp.setId(-1);
bool flag = false;
file.open("cigarettes.dat", ios::out | ios::binary);
if(file.is_open()){
file.seekg(0, ios::end);
if(file.tellg()!=0){
file.seekg(0, ios::beg);
while(!file.eof()){
file.read(reinterpret_cast<char *>(&temp), sizeof(temp));
if(temp.getId() == this->getId()){
flag=true;
file.seekp(-sizeof(temp),ios::cur);
file.write(reinterpret_cast<char *>(this), sizeof(temp));
break;
}
}
}
if(!flag){
file.write(reinterpret_cast<char *>(this), sizeof(temp));
}
}
file.flush();
file.close();
}
But when I try to retrieve my stored object with another function member:
list<Product> Product::loadProducsts(){
fstream file;
Product temp;
list<Product> products;
file.open("cigarettes.dat", ios::in | ios::binary);
if(file.is_open()){
while(!file.eof()){
file.read(reinterpret_cast<char *>(&temp), sizeof(temp));
products.push_front(temp);
}
}
file.close();
return products;
}
My array is filled with only one object empty. What's the problem?

There are any number of problems with your code, starting with
the fact that just dumping bits to a file generally will not
give you anything useful that you can read back. The fact that
you need a reinterpret_cast to use it should have tipped you
off. In addition:
You only open the file for output, and then try to read from
it. (Opening a file for output truncates it, so you've already
lost all of your previous data.)
I'm not sure what you think you're doing with while (!file.eof()),
but it's surely not correct. If for some reason, the
file.read fails without hitting the end of file, you'll end up
in an endless loop, for example.
And you're using the results of file.read without verifying
that the function worked.
You close the file without being at the end. This truncates
a file open in output mode.
Same problems in the read loop of the second snippet. If the
file is empty, you'll still "read" one object, pushing the
default constructed temp into products.
Not knowing what Product looks like, nor the initial contents
of the file, it's hard to say what is really happening. But
most likely:
You truncate the file with the open. It now has a length of
0.
Since file.tellg() returns 0, you never even try to read
it. (If you'd tried to read it, an error would have been set,
which would have made all successive operations no-ops.)
You then write the single element, and close the file.
The only thing I'm not too sure of: in this scenario, the file
actually does contain one element. So when you try to read it,
the first file.read succeeds, probably without setting
eofbit, since file.read doesn't need any look ahead. And if
eofbit isn't set, I would expect you to loop a second time,
and push the unmodified bits in temp into products a second
time.
EDIT:
FWIW: if we assume that you're in the very restricted case
where just writing the data bits to the disk is valid (which
normally means that you'll be rereading them later in the same
process, but never from a different processs), and that the
id can never be -1 in a valid Product, what you probably
want to do is:
Product temp;
temp.setId( -1 ); // This sort of thing should really be handled by a constructor
std::fstream file( "cigartettes.dat", ios::out | ios::in | ios::binary );
while ( file.read( reinterpret_cast<char*>( &temp ), sizeof(temp) && temp.getId() != getId() ) {
}
if ( file.gcount() != 0 ) {
// Error somewhere, we ended up reading a partial record
} else if ( temp.getId() == getId() ) {
file.seekp( -static_cast<int>( sizeof(temp) ) );
} else {
file.clear();
file.flush();
}
file.write( reinterpret_cast<char const*>( this ), sizeof(*this) );
file.close();
if ( !file ) {
// Something went wrong somewhere...
}
Several comments:
Opening in both input and output is necessary. Opening only
in output means that 1) the file will be truncated, and 2) any
attempt to read it will fail.
file.read will fail if it cannot read the correct number of
bytes. If it fails, it might have read some bytes anyway (and
overwritten the id field in Product, and left the current
pointer at some position which isn't a modulo of your object
size). So you should check for this using the value from
file.gcount() (which returns the number of bytes read by the
last unformatted read operation—in the case of the read
you're doing, this can only be different from sizeof(Product)
if the read failed.
When specifying a negative value to seek backwards: you have
to convert the results of sizeof to a signed type before doing
the -. Otherwise, you'll end up with some astronomical
positive value, which will cause you to try to seek beyond the
end of file (which will fail).
When the read fails, and the number of bytes read is 0, you've
reached the end of file. And set the failbit, which will
cause all future operations to fail. So we have to clear the
error if we're going to write to extend the data. (If we
haven't reached end of file, of course, there's nothing to
clear.)
When doing bidirectional input, after a read, you must execute
either a seek or a flush before a write. (Don't ask me why;
it's just what the standard says.)
Finally, it's good practice to verify the status of the file
after closing, when all buffers have been fully flushed and
passed to the OS. If for some reason, a write has failed
somewhere, you want to know about it, to inform the user that
the file that was output is corrupt.
I might add that the usual way of modifying just one record in
a file is to copy the file to a new file, replacing or appending
the changed record, and then delete the old file and rename the
new. Trying to modify a file, as you are doing, can mean that
you loose all of the data if something goes wrong.

Related

How to increse numbers in file?

I need to read file in parts ( for example by 4 bytes) and then increment numbers in files by one and then write back;
this part only fills in file by 1; How to increase this number on 1?
void Prepare()
{
//ifstream fileRead("\FILE", ios::in | ios::binary);
ofstream fileOut("\FILE.bin", ios::out | ios::binary);
int count = 10485760;
for (int i = 0; i < count-1; i++)
{
fileOut << 1;
}
fileOut.close();
}
If I understand your question, you need to read the file then write it out, changing the data. You can't really do it the way you've started.
There are two basic ways to do this. You can read the entire file into memory, then manipulate the memory, close the file, open it again for output this time (truncating it) and write it back out. This is easiest, but I don't think it's the approach you're looking for.
The other choice is to manipulate the file in place. That's trickier, but not that hard. You need to read about random access I/O (input/output). If you google for c++ random access file you'll get some good hits, but I'll show you a little bit.
// Open the file.
std::ifstream file{"file.dat"};
// Jump to a particular location in the file. Beginning is 0.
file.seekg(128);
// Read 4 bytes
char bytes[4];
file.read(bytes, 4);
// Manipulate it (more below)
int number = bytesToInt(bytes);
++number;
intToBytes(number, bytes);
// Seek again
file.seekg(128);
file.write(bytes, 4);
So the only remaining trick is that you have to convert the bytes to a number and then back into bytes. Due to endianness, it's not safe to read directly into the number. You also need to know the endianness of the data in the file. That's a separate topic you can look up if you're not already familiar with it.
(Specifically, you need to implement those two methods after verifying how the data is stored in your file.)
There may be other ways to do this, but the key to this method is the random access file.

C++ file stream for reading/writing

I need to open a file for both reading/writing using fstream and read each character then write that character back to the file. for example i have this code.
fstream in("test.txt",ios::in | ios::out);
if(!in)
cout<<"error...";
else
{
char ch;
in.seekg(0,ios::end);
int end=in.tellg();//get the length
in.seekg(0);//get back to the start
for(int i=0;i<end;i++)
{
//in.seekg(in.tellg());//if i uncomment this the code will work
if(!in.get(ch).fail())//read a character
{
in.seekp(static_cast<int>(in.tellg())-1);//move the pointer back to the previously read position,so i could write on it
if(in.put(ch).fail())//write back,this also move position to the next character to be read/write
break;//break on error
}
}
}
I have a file named "test.txt" which contains "ABCD". As i understand it both put() and get() methods of the stream object move the file pointer forward(i see that by getting the return value of tellg() or tellp() functions after each get() or put() method call). My question is when i comment out the code that will seek the stream pointer to "where it is now"(in.seekg(in.tellg()), the code will result incorrect results. I don't understand why this is since tellg() is showing the correct position of the character to be read next.what is the purpose of explicitly seeking to it? I am using visual studio 2005.
The incorrect result is it writes to the file "ABBB" instead of "ABCD".
The output buffer has to be flushed when switching between write and read.
fstream in("test.txt",ios::in | ios::out);
if(!in)
cout<<"error...";
else
{
char ch;
in.seekg(0,ios::end);
int end=in.tellg();//get the length
in.seekg(0);//get back to the start
for(int i=0;i<end;i++)
{
if(!in.get(ch).fail())//read a character
{
in.seekp(static_cast<int>(in.tellg())-1);//move the pointer back to the previously read position,so i could write on it
if(in.put(ch).fail())//write back,this also move position to the next character to be read/write
break;//break on error
in.flush();
}
}
}

How to check if there isn't data in file to read

std::fstream fin("emptyFile", std::fstream::in);
std::cout << fin.eof() << std::endl;
This prints 0. So using eof function I can't check if file is empty. Or after reading some data I need to check if there is no more data in it.
There are two ways to check if you "can read something" from a file:
Try to read it, and if it fails, it wasn't OK... (e.g fin >> var;)
Check the size of the file, using fin.seekg(0, ios_base::end); followed by size_t len = fin.tellg(); (and then move back to the beginning with fin.seekg(0, ios_base::beg);)
However, if you are trying to read an integer from a text-file, the second method may not work - the file could be 2MB long, and still not contain a single integer value, because it's all spaces and newlines, etc.
Note that fin.eof() tells you if there has been an attempt to read BEYOND the end of the file.
eof() gives you the wrong result because eofbit is not set yet. If you read something you will pass the end of the file and eofbit will be set.
Avoid eof() and use the following:
std::streampos current = fin.tellg();
fin.seekg (0, fin.end);
bool empty = !fin.tellg(); // true if empty file
fin.seekg (current, fin.beg); //restore stream position

reading until the end of file in C++

I'm trying to read till the end of a file for a phonebook app that im converting from C to C++. When I print the the results from the file i get this:
johnny smith
(Home)3
(Cell)4
x☺> x☺>
(Home)4
(Cell)4
it should print:
johnny smith
(Home)3
(Cell)4
Right now I'm using while(!infile.eof()) which i've read is a poor practice, but when I use infile.getline() I get a repeat of the first and last name, and the format is all jacked up. Is there anyway(or another way) to get rid of the junk at the end of the input or another way to read till the end of file in C++ that fixes this. I've been reading about different solutions, but the one a lot of sites seem to agree on is fgets, which is what I had with the original C version, but obviously fgets doesn't work with ifstream which is what I'm using. here is the code:
void contacts:: readfile(contacts*friends ,int* counter, int i,char buffer[],char user_entry3[])
{
ifstream read;
read.open(user_entry3,ios::in);
int len;
contacts temp;
*counter=0;
i=0;
while (!read.eof()) {
temp.First_Name=(char*)malloc(36);
temp.Last_Name=(char*)malloc(36);
read>>temp.First_Name>>temp.Last_Name;
read>>buffer;
len=strlen(buffer);
if(buffer[len-1]=='\n')
buffer[len-1]='\0';
temp.home=(char*)malloc(20);
strcpy(temp.home, buffer);
read>>buffer;
len=strlen(buffer);
if(buffer[len-1]=='\n')
buffer[len-1]='\0';
temp.cell=(char*)malloc(20);
strcpy(temp.cell, buffer);
friends[i].First_Name=(char*)malloc(MAXNAME);
friends[i].Last_Name=(char*)malloc(MAXNAME);
friends[i].home=(char*)malloc(MAXPHONE);
friends[i].cell=(char*)malloc(MAXPHONE);
//adds file content to the structure
strcpy(friends[*counter].First_Name,temp.First_Name);
strcpy(friends[*counter].Last_Name,temp.Last_Name);
strcpy(friends[*counter].home,temp.home);
strcpy(friends[*counter].cell,temp.cell);
(*counter)++;
i++;
}
//closes file and frees memory
read.close();
free(temp.Last_Name);
free(temp.First_Name);
free(temp.home);
free(temp.cell);
}
Don't use !eof(). It checks whether the last read failure was due to reaching the end of the file. It does not predict the future.
Don't use malloc in C++. If you do, check the return value for errors!
Don't use operator>> for char *. There's no size check so that's just asking for buffer overflows.
The '\n' check on buffer is useless. operator>> for strings stops at whitespace.
You're blindly strcpying a string of unknown length into temp.home of size 20. That's another buffer overflow.
... I kind of stopped reading there. If you want to read stuff from a file but stop on eof/error, you can do something like this:
.
string a, b, c;
while (true) {
if (!(in >> a)) break;
if (!(in >> b)) break;
if (!(in >> c)) break;
do_stuff_with(a, b, c);
}
Do not use eof() to determine if you reached end of file. Instead, read what you want to read and then check if you successfully read the data. Obce reading failed you may use eof() to determine if the error is down to having reached the end of the file before producing an error report about a format error.
Since you mentioned that you read that using !infile.eof() is good practice: Can you point us at the source of this wrong information? This information need correction.

C++ Binary File method is removing content from the file?

I have an assignment where I am writing input on various things (in the form of structs) and then writing to a binary file. I have to be able to both read and write to the file while the program is open. One of the methods needs to print out all of the clients in the binary file. It seems to be working, except whenever I call that method, it seems to erase the contents of the file and prevent more from being written to it. Here's the applicable snippets:
fstream binaryFile;
binaryFile.open("HomeBuyer", ios::in | ios::app | ios::binary);
The same file is supposed to be usable between times you run the program, so I should open it with ios::app, correct?
Here's the method to add an entry:
void addClient(fstream &binaryFile) {
HomeBuyer newClient; //Struct the data is stored in
// -- Snip -- Just some input statements to get the client details //
binaryFile.seekp(0L, ios::end); //This should sent the write position to the
//end of the file, correct?
binaryFile.write(reinterpret_cast<char *>(&newClient), sizeof(newClient));
cout << "The records have been saved." << endl << endl;
}
And now the method to print all the entries:
void displayAllClients(fstream &binaryFile) {
HomeBuyer printAll;
binaryFile.seekg(0L, ios::beg);
binaryFile.read(reinterpret_cast<char *>(&printAll),sizeof(printAll));
while(!binaryFile.eof()) { //Print all the entries while not at end of file
if(!printAll.deleted) {
// -- Snip -- Just some code to output, this works fine //
}
//Read the next entry
binaryFile.read(reinterpret_cast<char *>(&printAll),sizeof(printAll));
}
cout << "That's all of them!" << endl << endl;
}
If I step through the program, I can input as many clients as I want, and it will output them all the first time I call displayAllClients(). But as soon as I call displayAllClients() once, it seems to clear out the binary file, and any further attempts at displaying clients gives me no results.
Am I using seekp and seekg incorrectly?
From what I understand, this should set my write position to the end of the file:
binaryFile.seekp(0L, ios::end);
And this should set my read position to the beginning:
binaryFile.seekg(0L, ios::beg);
Thanks!
Pasting comment in as this resolved the issue.
You need to call binaryFile.clear() before seekp() and seekg() if EOF is set, otherwise they won't work.
This is the documentation for ios::app
ios::app
All output operations are performed at the end of the file, appending the
content to the current content of the file. This flag can only be used in
streams open for output-only operations.
Since this is homework, I'll let you draw your own conclusions.