@
Zhuge
CString is the MFC string class, as
std::
string is the STL string class.
@
franknguyen
The problem is that CString is a class, not a native type, so you cannot treat the
member struct as a POD type and just
file.write() and
file.read() it. You need to be a little more careful about the serialization of your object.
But first, an aside:
You should not be storing the user's password directly. I hope you are storing a proper password hash.
http://en.wikipedia.org/wiki/Password#Form_of_stored_passwords
OK, back to serialization.
Keep in mind that it makes no sense to store a pointer in the file, since your program is not placed nor uses memory the same way each time it is run. If you save a pointer, then load it, it does
not point to the same thing it did the first time. Even if it did, though, the data it pointed at
was never saved, so it was lost.
Hence, the
next field is not something you can directly store in the file.
The CString objects are themselves wrappers around a
pointer to text (a char array), so directly reading and writing the CString structure looses the textual data that is being addressed. (You can imagine a CString to look something like this:
1 2 3 4 5 6 7 8
|
class CString:
{
char* text; // somewhere on the heap where the user's text is kept
size_t length; // the number of chars filled with meaningful data at *text
size_t capacity; // the number of chars available for use at *text
public:
... // methods
};
|
So when you
write it to file, you save a useless pointer and some otherwise useful size information, but the actual text is lost.)
Hence, the
name,
userName,
pass, and
address fields are not something you can directly store in the file.
So, the question is: How do you store this 'pointed at' textual information?
The answer would be much simpler if the password were not stored as a hash (I would say just to read and write text files), but as it should be a hash that contains potentially any character, we need to stick to binary files and do something a little more careful with the strings. We will need a function that
serializes the structure into a string, which we can then write. (We will also need a function to de-serialize the string into a member after we read it.)
The serialization should be pretty simple.
All 'date' and 'int' objects are POD types, so they can be written and read exactly as they are. The 'member*' object we will not write to file, since it is not necessary in the file anyway (read the next section below for more).
That just leaves the 'CString' objects, which we can serialize pretty easily. How? Just add the
length to the beginning of the string! As I am not too familiar with MFC, I'll use a std::string here, but you should be able to translate the std::string methods to CString easily enough:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
std::string serialize_string( const std::string& s )
{
std::string result;
// The first four bytes of the string will store the LENGTH of 's'
// as a little-endian word
result.resize( 4 );
result[ 0 ] = s.length() & 0xFF;
result[ 1 ] = (s.length() >> 8) & 0xFF;
result[ 2 ] = (s.length() >> 16) & 0xFF;
result[ 3 ] = (s.length() >> 24) & 0xFF;
// The rest of the string is exactly the same as 's'
result.append( s );
// That's it!
return result;
}
|
Now, to serialize a member, you do very much the same thing: create a string, append the serialize_string()ed name, userName, and pass to it, encode the dob manually into it, add the serialize_string()ed address, encode the sex and status. Finally, serialize_string() your total string and write it to file.
To
read a member, you just need to go backwards. This is where the fancy serialize_string() stuff pays off. Read the length stuff we put there from the file:
1 2 3 4 5 6 7 8 9
|
size_t unserialize_length( ifstream& file )
{
size_t result = 0;
result |= file.get();
result |= file.get() << 8;
result |= file.get() << 16;
result |= file.get() << 24;
return result;
}
|
Now we know exactly how much string to get. :-)
1 2 3 4 5 6 7 8 9 10
|
std::string unserialize_string_from_file( ifstream& file )
{
size_t length = unserialize_length( file );
char* buffer = new char[ length + 1 ];
file.read( buffer, length );
buffer[ length ] = '\0';
std::string result( buffer );
delete [] buffer;
return result;
}
|
Etc.
n and
member* next
Forget
n and
next. Treat the file as simply a list of members. The
n of members, and their order, are simply the number of members in the file, ordered exactly as they are in the file, first to last. Obvious, no?
Writing the file then becomes something nice
file.open
for (member* m = first; m != NULL; m = m->next)
{
string binary_data = serialize *m
file.write binary_data
}
file.close
Reading the file also becomes something nice, though a little more bookkeepping is involved to construct the linked list as we go:
file.open
member* m = NULL;
string binary_data
while (file.read binary_data)
{
if (m == NULL) first = m = new member;
else { m->next = new member; m = m->next; }
n += 1;
deserialize binary_data into *m
}
m->next = NULL;
file.close
Phew. Hope this helps. (This is all just off the top of my head, and was designed to be readable, not blazingly efficient. But once you get the right ideas, you can run with them. :O)