A small file i/o problem in C++

Started by
10 comments, last by GameDev.net 17 years, 10 months ago
This is just a quick question you guys shouldn't take more than a couple minutes to help me solve. But it'd be a big help to me if you guys did. See, I decided that I'm doing a text-based RPG due to my failed attempts to do graphics programming. I spent some time making a player class and a driver for it. For the most part it works, except the part where I use a file to save/load a character's stats. The issue I'm having is that the method that writes to the file isn't writing, but isn't returning an error either. It reads the file, checks if the character's data is in there, and then either overwrites it (though I could be doing that part of it wrong) or it writes the info to the end of the file. Here's the code for it:
[SOURCE]

int Player::SaveToFile(char * fname)
{
	fstream file;
	int i = 0;
	char  temp[1001];
	file.open(fname);
	if(!file.is_open()) return 1;
	while(!file.eof())
	{
		file.getline(temp, 60, ':');
		if(strcmp(name, temp) == 0) break;
		file.getline(temp, 1000);
		++i;
	}
	file.seekg(0, ios_base::beg);
	for(int j = 0; j < i; ++j) 
		file.getline(temp, 1000);
	file << name << ':' << HP << ':' << MP
		 << ':' << level << ':' << exp
		 << ':' << hit << ':' << str 
		 << ':' << def << ':' << intell 
		 << ':' << focus << "\n" << endl;
	file.close();
	return 0;
}

[/SOURCE]
[/source] Okay...it compiles okay, but it says that it exits in the destructor I call later which reads from the file and creates an object based upon it. Here's that code:
[SOURCE]
Player::Player(char * fname, char * nam)
{
	fstream file;
	char temp[1001];
	file.open(fname);
	if(!file.is_open()) exit(1); //Untill I find a better solution
	while(!file.eof())
	{
		file.getline(temp, 60, ':');
		if(strcmp(nam, temp) == 0) break;
		file.getline(temp, 1000);
	}
	if(file.eof()) exit(2); //Needs same solution (THIS IS WHERE IT EXITS)
	name = nam;
	file >> HP; file >> MP; file >> level; file >> exp;
	file >> hit; file >> str; file >> def;
	file >> intell; file >> focus;
	file.close();
	curHP = HP; curMP = MP;
}

[/SOURCE]
[/source] As you can see, it exits on EOF, because nothing gets written to the file. I checked the file, and there's nothing in it. I added a few newlines to it, and still nothing. Why won't it write to the file? EDIT: Noticed a tiny flaw. Put the second temp array as 1000, not 1001. [Edited by - BringBackFuturama on June 11, 2006 12:50:27 AM]
Originality is dead.
Advertisement
Quote:
while(!file.eof())	{		// stuff	}if(file.eof()) exit(2);


In plain English: "Until you reach the end of the file, read stuff from the file and do stuff with it. Then, after that, if you have reached the end of the file, panic!"

But, you know, observing the end of the file when you have just finished "doing stuff until the end of the file" is exactly what you should expect to happen. :)

(Oh, I see, you wanted to break out when you found the right line, and then parse that line. Sorry: after reading the line, the file is positioned *after* that line. And I expect there's normally a very good chance that the line you want is the last line in the file ;) )

Anyway, you have big, BIG problems here. First of all, use std::string to represent textual data. :) Second, this reading and writing to the same file is HUGELY sketchy. Normally, when you write to an output file, you just overwrite the whole thing. "If the character's data is in there", you'll want to replace it with the updated *anyway*. What are you hoping to gain by appending instead, and how in the hell are you expecting the code as written to make a decision about that?

Oh, wait. Did you mean that the file represents several characters, and you want to overwrite the line with the character's previous data if you update for a character with the same name? Sorry, file I/O doesn't work like that. The new data that you write in won't necessarily be the same length, and the other stuff won't be automatically moved around to fit.

The normal way of handling files like this is:

- Open the file for reading. Read EVERYTHING in. Into an intelligently designed data structure.
- Change the parts that need changing.
- CLOSE AND REOPEN the file for writing. Write EVERYTHING out, overwriting the entire old contents of the file.

For the reading, you have huge problems as well. First of all, the answer to your implicit question about what to do instead of exit() is: throw an exception. Then, reading in the values simply doesn't work like what is shown: you have to account for the ':' characters.

I'm going to hold your hand for this, because there's no way I can sanely expect you to figure it all out on your own.

// You need to make sure you have 'string' and 'sstream' in your includes now.// Please note the change to the return type and values.bool Player::SaveToFile(const string& filename) {  // Don't declare a "blank" fstream and then open it later. Instead do this:  ifstream file(filename.c_str());  // Note that by specifying an *i*fstream, I implicitly requested just read  // access this time around. Trying to read and write a file at the same time  // is usually asking for pain.  int i = 0;  // Here's a buffer where I will store lines from the file that I don't  // want to change. It also provides a stream interface, so I can "write out"  // to it temporarily while reading in from the real file, and then write the  // whole thing to file when I'm done.  sstream modifiedFile;  // Here's a buffer for the current line, since we're processing the file  // a line at a time.  string line;  // And also a buffer for the current name.  string nameFromFile;  if (!file.is_open()) return false;  while(!file.eof()) {    // To read into a std::string, we use the free function version of    // getline(), rather than the stream member function.    getline(file, nameFromFile, ':');    getline(file, line); // the rest of the input line.    // Now here's some magic: with std::string, this works!    if (name == nameFromFile) {      // Then we should append our modified record to the output.      modifiedFile << name << ':' << HP << ' ' << MP                   << ' ' << level << ' ' << exp                   << ' ' << hit << ' ' << str                    << ' ' << def << ' ' << intell                    << ' ' << focus << '\n';      // Note that there is no reason you can't put '\n' in single quotes the      // same way that you put ':' in single quotes. It is 2 bytes of source       // code, but it *represents* one byte of data.      // Note that I'm putting spaces instead of every colon except the last.      // That makes it easier to parse in the reading routine, because the >>      // operator skips whitespace, but not punctuation between numbers.      // The ':' is only useful to separate the text name from the numeric      // items in the rest of the line.    } else {      // Otherwise, we should append the line from the file. That consists of      // the name we just read (up to the ':'), followed by the rest of the line      // (up to the '\n').      modifiedFile << nameFromFile << ':' << line << '\n';    }  }  // Now I can close the file and reopen it, with an ofstream.  file.close();  ofstream ofile(filename.c_str());  // And dump all of the buffered file contents at once:  ofile << modifiedFile.str() << flush;  // "flush" is like "endl", but without actually writing a newline: it  // only flushes the buffer and makes sure everything is written.   // (You don't need to flush stringstream writes, but you do with files and  // the console.)  // I don't need to write 'ofile.close()' here, because file closing is   // automatically done by the destructor of fstream objects. The reason I  // explicitly closed the 'ifstream file' is because that has to happen  // *before* we reopen the same file, and the destructor doesn't happen until  // end of scope.}Player::Player(const string& filename, const string& name) : name(name) {  // I use the initializer list to initialize things that are known right away.  // In our case, that's the Player name: as long as the object will be  // constructed OK, the 'name' will be set to the provided name. There's no  // need to read the file to determine that much - if it doesn't match in   // the file then we're going to exception out anyway :)  ifstream file(filename.c_str());  if (!file.is_open()) throw std::exception(); // this can be refined later  // Borrowing a technique from before...  string line;  string nameFromFile;  // This is one example of a common idiom with file reading:  // It says "while possible to do this read... (oh, and please actually do the  // read if you can)"  while (getline(file, nameFromFile, ':')) {    if (nameFromFile == name) {      // OK, use the rest of this line to initialize the object.      file >> HP >> MP >> level >> exp           >> hit >> str >> def >> intell            >> focus;      curHP = HP; curMP = MP;      // Nothing stops you from chaining the reads together the same way you      // do with writes.      return; // We finished.      // No need to close the file here, as noted before.    } else {      // Skip the rest of this line.      // Actually, there are better ways to do that, but I'm leaving it like       // this for symmetry and to avoid overloading you ;)      getline(file, line);    }  }  // If we get here, the line wasn't found.  throw std::exception();  // No need to close the file here, as noted before.}
Quote:Original post by Zahlman
Quote:
while(!file.eof())	{		// stuff	}if(file.eof()) exit(2);


In plain English: "Until you reach the end of the file, read stuff from the file and do stuff with it. Then, after that, if you have reached the end of the file, panic!"

But, you know, observing the end of the file when you have just finished "doing stuff until the end of the file" is exactly what you should expect to happen. :)

(Oh, I see, you wanted to break out when you found the right line, and then parse that line. Sorry: after reading the line, the file is positioned *after* that line. And I expect there's normally a very good chance that the line you want is the last line in the file ;) )

Anyway, you have big, BIG problems here. First of all, use std::string to represent textual data. :) Second, this reading and writing to the same file is HUGELY sketchy. Normally, when you write to an output file, you just overwrite the whole thing. "If the character's data is in there", you'll want to replace it with the updated *anyway*. What are you hoping to gain by appending instead, and how in the hell are you expecting the code as written to make a decision about that?

Oh, wait. Did you mean that the file represents several characters, and you want to overwrite the line with the character's previous data if you update for a character with the same name? Sorry, file I/O doesn't work like that. The new data that you write in won't necessarily be the same length, and the other stuff won't be automatically moved around to fit.

The normal way of handling files like this is:

- Open the file for reading. Read EVERYTHING in. Into an intelligently designed data structure.
- Change the parts that need changing.
- CLOSE AND REOPEN the file for writing. Write EVERYTHING out, overwriting the entire old contents of the file.

For the reading, you have huge problems as well. First of all, the answer to your implicit question about what to do instead of exit() is: throw an exception. Then, reading in the values simply doesn't work like what is shown: you have to account for the ':' characters.

I'm going to hold your hand for this, because there's no way I can sanely expect you to figure it all out on your own.

*** Source Snippet Removed ***


Wow...I guess I was way off. :| Thanks a ton! I just have a few questions..I want to be able to understand all this if I'm gonna use it in my code:

1. With a reference to a string, can I still set a default argument for the file name? (I have one in the class, but not defined in the defs)

2. What is an "sstream?" String Stream? I'm not familiar with it. Does it basically just hold strings?

3. I used C-style strings because I wasn't familiar with the string class (plus I couldn't figure out how to use it right). Does the c_str() method basically convert to a C-style string?

Originality is dead.
Also, I included sstream, and it's giving me these erorrs:

rpgdefs.cpp(130) : error C2065: 'sstream' : undeclared identifier
rpgdefs.cpp(130) : error C2146: syntax error : missing ';' before identifier 'modifiedFile'
rpgdefs.cpp(130) : error C2065: 'modifiedFile' : undeclared identifier
rpgdefs.cpp(152) : error C2228: left of '.str' must have class/struct/union type is ''unknown-type''

Obviously...it didn't find it. Is there a namespace I'm supposed to use besides std that it's in, or if not, how do I get it to recognize it?
Originality is dead.
Quote:Original post by BringBackFuturama
Also, I included sstream, and it's giving me these erorrs:

rpgdefs.cpp(130) : error C2065: 'sstream' : undeclared identifier
rpgdefs.cpp(130) : error C2146: syntax error : missing ';' before identifier 'modifiedFile'
rpgdefs.cpp(130) : error C2065: 'modifiedFile' : undeclared identifier
rpgdefs.cpp(152) : error C2228: left of '.str' must have class/struct/union type is ''unknown-type''

Obviously...it didn't find it. Is there a namespace I'm supposed to use besides std that it's in, or if not, how do I get it to recognize it?


the include is

#include <sstream>

but the type is

std::stringstream modifiedFile;

Quote:Original post by rip-off
Quote:Original post by BringBackFuturama
Also, I included sstream, and it's giving me these erorrs:

rpgdefs.cpp(130) : error C2065: 'sstream' : undeclared identifier
rpgdefs.cpp(130) : error C2146: syntax error : missing ';' before identifier 'modifiedFile'
rpgdefs.cpp(130) : error C2065: 'modifiedFile' : undeclared identifier
rpgdefs.cpp(152) : error C2228: left of '.str' must have class/struct/union type is ''unknown-type''

Obviously...it didn't find it. Is there a namespace I'm supposed to use besides std that it's in, or if not, how do I get it to recognize it?


the include is

#include <sstream>

but the type is

std::stringstream modifiedFile;


So I type stringstream instead of "sstream" then?
Originality is dead.
Alright, all it's writing to the file is the ':' characters. Then when it tries to read from the file, obviously, it exits and all the data for the new object I instantiate from it in the object just becomes the max negative number for an int. Interesting....
Originality is dead.
This is the output:

:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:

I've been thinking about it, and I get the idea that this little piece of code is the problem:

  while(!file.eof())   {    getline(file, nameFromFile, ':');    getline(file, line);     if (name == nameFromFile)	{      modifiedFile << name << ':' << HP << ' ' << MP                   << ' ' << level << ' ' << exp                   << ' ' << hit << ' ' << str                    << ' ' << def << ' ' << intell                    << ' ' << focus << '\n';    } 	else modifiedFile << nameFromFile << ':' << line << '\n';  }  file.close();  ofstream ofile(filename.c_str());  ofile << modifiedFile.str() << flush;  file.close();
Originality is dead.
Quote:Original post by BringBackFuturama
1. With a reference to a string, can I still set a default argument for the file name? (I have one in the class, but not defined in the defs)


Yes, default arguments can be set up regardless of the argument type. All you need to do is put something on the RHS of the '=' that is convertible to the argument type. So for example: 'void injure(const std::string& who = "Mark");'.

Quote:2. What is an "sstream?" String Stream? I'm not familiar with it. Does it basically just hold strings?


As you've already discovered, the type name is 'stringstream' and I messed that up when typing out the code :) 'sstream' is only the name of the header file.

As I described in the comments, it's a stream-like thing which reads from/writes to a string buffer, rather than to a file or the console.

Quote:3. I used C-style strings because I wasn't familiar with the string class (plus I couldn't figure out how to use it right). Does the c_str() method basically convert to a C-style string?


Basically, yes. (AFAIK, it gives you access to the underlying C-style string representation, as a const char* - i.e., making you promise that you won't try to change the data, because there's no way that the string object could "know about" such changes and you could seriously mess things up if you did.)

As for your writing problem, check the contents of the input file, and check the contents of 'line' on each iteration of the loop. Learn to debug stuff, you're not helpless :)
Well I'm still working on it. I'm incompetent I guess. I changed a couple things in the code, and now it's supposed to have a first letter as a ':' to identify that it's a line for a character.

Here's the code:

bool Player::SaveToFile(const string& filename) {  ifstream file(filename.c_str());  char firstch;  bool found = false;  stringstream modifiedFile;  string line;  string nameFromFile;  if (!file.is_open()) return false;  while(!file.eof())   {    file >> firstch;	if(firstch == ':')	{		getline(file, nameFromFile, ':');		if (name == nameFromFile)		{		  modifiedFile << ':' << name		  	             << ": " << HP << ' ' << MP		                 << ' ' << level << ' ' << exp		                 << ' ' << hit << ' ' << str 		                 << ' ' << def << ' ' << intell 		                 << ' ' << focus << '\n';		  found = true;		}		else 		{			getline(file, line);			modifiedFile << firstch << nameFromFile << ':' << line << '\n';		}		continue;	}		getline(file, line);	modifiedFile << firstch << line << '\n';  }  file.close();  if(found)  {		modifiedFile << ':' << name		             << ": " << HP << ' ' << MP                     << ' ' << level << ' ' << exp                     << ' ' << hit << ' ' << str                      << ' ' << def << ' ' << intell                      << ' ' << focus << '\n';  }  ofstream ofile(filename.c_str());  ofile << modifiedFile.str() << flush;  file.close();  modifiedFile.flush();  return true;}


The output to the file is the following:

Ì
Ì
Ì
Ì


Wierd....:|
Originality is dead.

This topic is closed to new replies.

Advertisement