Delete line in file

Hello,
I want to write information in a file for my game like:
Dwarve G 656 275 481 481 32 2 which is working with my code "File << *PlayerS.get..." as below.
This works, but only if the previous while loop is disabled with /* */.
The while loop shall check if the character already exists by checking the character name in the file. If so, the line shall be deleted before writing the new data into the file.
The loop itself does not work, so nothing is deleted, and also the writing "File << *PlayerS.get..." is not executed although it is outside of the while loop.

I would appreciate an explanation to both points, so why the loop does not work and also why the code after the loop is not executed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// code is elsewhere, but is an example for my get functions as shown below:
std::string* getCharacterName() { return &characterName; };

// here is my problem
std::fstream File;
File.open("Savefile.txt", std::ofstream::app);
if (File.is_open())
{
    std::string line;
    while (std::getline(File, line))
	{	
	 line.replace(line.find(*PlayerS.getCharacterName()), line.length(), "");
	}
  // The following creates e.g. this input: Dwarve G 656 275 481 481 32 2 
    File << *PlayerS.getCharacterType() << " " << *PlayerS.getCharacterName()
 << " " << *PlayerS.getStrength() << " " << *PlayerS.getSkill() << " " 
<< *PlayerS.getLife() << " " << *PlayerS.getMaxlife() << " " 
<< *PlayerS.getExperience() << " " << *PlayerS.getLevel() << std::endl;
}
File.close();
Last edited on
There's multiple problems and errors in concept in that code.

First, since you opened the file with the append flag, the file pointer is at the end of the file. std::getline() fails the first time (since you can't read when the file pointer is at the end of the file) and leaves File in the errored state, which causes all further file operations to fail.

Second, even if std::getline() succeeded, line 13 is not touching the file. It's only modifying the contents of the std::string object in memory.

Finally, consider this:
 
std::vector<int> data = { 1, 2, 3, 4, 5, 6, 7, 8 };
Suppose you want to replace the subvector {3, 4, 5} with {3, 7, 3, 4, 5}. You would need to resize the vector and shift {6, 7, 8} forward, right? And to replace it with {0} you would need to shift {6, 7, 8} and shrink the vector. However, if you only want to replace that block with {0, 0, 0} you can do that easily.
Files are simply arrays of bytes. Your file might look like this to your operating system: "Dwarve G 656 275 481 481 32 2\nElf S 56 205 581 0 7 5\n". Although a text editor will let you edit any line conveniently, what they typically do is load the entire file into some data structure in memory, and when you hit save they rewrite the entire file to the storage medium. If, in your program, you put the file pointer at the start of the file and write a line willy-nilly, your file might end up like this:
Minotaur Q 1000 275 481 481 32 2
f S 56 205 581 0 7 5
or like this:
Imp Q 656 275 481 481 32 2
 2
Elf S 56 205 581 0 7 5

Simply put, you can't replace lines in a file. If you need to modify a line-based text file you need to load it into memory, do the modifications you need to the data structures, and then write it back out in its entirety.
Last edited on
"Text editors", like sed and perl, prefer to use a temporary file as the "memory":
https://www.arl.wustl.edu/projects/fpx/references/perl/cookbook/ch07_09.htm

That (a) works with files that do not fit into RAM and (b) keeps the original file content.
Hello Hay9,

First off learn to use some blank lines. I makes the code easier to read.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// code is elsewhere, but is an example for my get functions as shown below:
std::string* getCharacterName() { return &characterName; };

// here is my problem
std::fstream File;

File.open("Savefile.txt", std::ofstream::app);  // <--- Problem starts here.
//File.open("Savefile.txt", std::ios::in | std::ios::out);  // <--- Need to open the file stream for both input and output.

if (File.is_open())  // <--- Stream is open until you reach the last line. Even if you set the "eof" bit reading past end of file.
{
	std::string line;

	while (std::getline(File, line))  // <--- Works, but leaves the file pointer at the beginning of the next line.
	{
		line.replace(line.find(*PlayerS.getCharacterName()), line.length(), "");
	}

	// The following creates e.g. this input: Dwarve G 656 275 481 481 32 2 
	File
		<< *PlayerS.getCharacterType() << " " << *PlayerS.getCharacterName()
		<< " " << *PlayerS.getStrength() << " " << *PlayerS.getSkill() << " "
		<< *PlayerS.getLife() << " " << *PlayerS.getMaxlife() << " "
		<< *PlayerS.getExperience() << " " << *PlayerS.getLevel() << std::endl;
}

File.close();

The comments should help a bit.

Without the code to read the file no one has any aide what you did or if you stored the information in variables, a class or a struct. It looks like you used a class, so the easiest way may be to write the information from all the classes to the file overwriting what is already there.

If you are trying to change one line of the file then you need to deal with repositioning the file pointer, but that could be a problem if the new line is larger than the old line.

Another possibility is to read the file for input and write the unchanged lines to a new temp file. Then when you reach the changed information write it to the temp file. then finish with what is left. Finally deleting the original file and renaming the temp file.

I do have a question. What is the name "Dwarve G" or "Dwarve"? This will make a difference with the "find" function.

It would help to know how you read the file and what you do with the information to get a better idea of how to write to a file.

Andy
Hello all,

thank you for the comments. They helped a lot to sort things out. As you noticed I am beginner (by self learning), so I have of course a lack of understanding. @helios: Your comment increased my understanding a lot, especially that I cannot just replace lines in a file.
@Andy: To answer your question: Dwarve is the "Type" and G the "Name" attribute of the object.

Considering the purpose for me:
It is for training how to handle files. As I read, normally I should use a database and encrypt it and also that a simple textfile is not beneficial as it is hard to handle if the writing input exceeds 1 line in length (as helios also wrote).
But with the small console game I create, it will not exceed a line in the textfile. I wanted to start completely from scratch without any frameworks as I thought that this is the best way for initial C++ training.

So, below is my solution which works. I shortened the code to have only the attributes "type" and "name" as the amount of information is not relevant for the function itself.
I decided to use my character class to create an object vector filled with all characters in the textfile the player is not playing with in this game. These are then rewritten in another file together with the player character after the game. Then I delete the original file and rename the tempfile to the original name.

Just for my understanding: Is the way to create a vector object a good way or does it use to much memory?
My game is very small, so of course now I do not have a problem, but maybe for a bigger game this way is not very convenient.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// this instance of the Character class is the "active" object the player is "playing with"
Character PlayerS;

// now I create a Character object vector in the header file
std::vector <Character> PlayerTemp


// Reads existing file into the vector object
std::fstream File;
File.open("Savefile.txt", std::ios::in);	
		
if (File.is_open())
   {
	std::string line;
	while (std::getline(File, line))
	{
	   std::stringstream linebuffer(line);
	   Character PlayerT;
	   linebuffer >> *PlayerT.getType() >> *PlayerT.getName();

           // Write all "not active" Character objects into vector    
           if (*PlayerT.getName() != *PlayerS.getName())
	        {	
		this->PlayerTemp.push_back(PlayerT);
		}
	}
   }
File.close();


// Write data into temp file
std::fstream Temp;
Temp.open("Temp.txt", std::ios::out);

if (Temp.is_open())
   {
   for (std::size_t i = 0; i < PlayerTemp.size(); i++)
	{
	Temp << *PlayerTemp[i].getType() << " " << *PlayerTemp[i].getName() << std::endl;
	}
   Temp << *PlayerS.getType() << " " << *PlayerS.getName() << std::endl;
   }
Temp.close();


remove("Savefile.txt");
rename("Temp.txt", "Savefile.txt");
Last edited on
Hello Hay9,

Since you are learning the same as I am let me cover some things that yo umay not be understanding correctly.

When it comes to file streams the are 3 basic types:

fstream
ifstream
ofstream



The "fstream" is like a generic file stream. It will open a file to use, but it is also stupid in that you have to tell it if you plan to use it for input, output or both. It does not work both ways just because you think it can or because you want it to.

Lets say that you want the "fstream" to be used as an input stream you would say: std::fstream("filename", std::ios::in);. Sorry in the beginning I got stuck on the std::ios::in or std::ios::out. There are other ways. More simply I believe you can use std::fstream("filename", File::in); or std::fstream("filename", fstream::in);, although these I have not used very often.

For the "ifstream" as the "i" implies it is an input stream. You do not need to tell it that you will be using it as an input stream. To use it all you need is std::ifstream File.

Now whether you use "fstream" or "ifstream" you need to follow the open with a check, shown later. Otherwise yo could continue with the program as if nothing is wrong you just will not be able to read anything from the file and your program will look as if it does not work.

The "ofstream" the "o" implies output and like the "ifstream" you do not need to tell it that it is for output. With the "ofstream"an also with the "fstream" you do need to tell it other things that are needed like if you want to truncate the file or append the file. You do this with the bitwise OR (|) operator. Parts of this page http://www.cplusplus.com/reference/fstream/ofstream/open/ should help. See the example in the link.

The next thing you may not find in your reading is how you can open a file stream.

Two options are:
1
2
3
std::ifstream inFile;

inFile.open("file Name")  // <--- Or you could a "std::string" variable here. 


Or: std::ifstream inFile("file Name"); // <--- Or you could a "std::string" variable here. This not only defines the file stream, but also opens the file when constructed. This would be the more preferred method because it is all done at one time, but either way works.

To start with your code would be something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// this instance of the Character class is the "active" object the player is "playing with"
Character PlayerS;

// now I create a Character object vector in the header file
std::vector <Character> PlayerTemp;  // <--- Carefull. You are missing the (;).

std::ifstream File("Savefile.txt");  // <--- Defines the file stream and opens the file. Best choice.

if (!inFile)
{
	std::cout << "\n File " << std::quoted("Savefile.txt") << " did not open" << std::endl;
	//std::this_thread::sleep_for(std::chrono::seconds(3));  // <--- Needs header files chrono" and "thread". Optional.
	return 1;  // <--- If in "main" leaves the program. If in a function returns to where it was called from.
}

In the if condition there are other ways of checking, but this would be the best choice as it has a better chance of catching a problem than the other ways.

You used if (File.is_open()). Works, but a narrow focus. Also to use this you would need an else statement should the if condition be false.

You can use an if/else to run the program, but it is not the best way of programming.

The "return" statement will cause you to leave the program to fix the problem. Returning (0) zero means that the program ended successfully and there was no problem. Any number great than (0) zero is considered a failure and that there was some kind of problem. You can use that number to help determine what went wrong or just to help find it in your code.

You may find these links helpful:
http://www.cplusplus.com/reference/fstream/
http://www.cplusplus.com/reference/fstream/ifstream/
http://www.cplusplus.com/reference/fstream/ofstream/

When I get into the while loop these line confuse and concern me.
1
2
3
Character PlayerT;

linebuffer >> *PlayerT.getType() >> *PlayerT.getName();

Small thing. Defining the class as "Character" is OK starting the class name with a capital letter. When it comes to the variable name it is better to start all regular variables with a lower case letter. In the end you are free to name your variables any way that you want.

What is generally used is that regular variables start with a lower case letter. Classes and structs start with a capital letter. And variables defined as a constant, with either "constexpr" or "const" are in all capital letters. Now whether you use "camelCase" or make use of the underscore is up to you. Just be consistent in it use.

The same is true when using {}s. There are many different styles to choose form.
https://en.wikipedia.org/wiki/Indentation_style#Brace_placement_in_compound_statements
My personal choice is the "Allman" style because it is the easiest to read and work with. You do not have to hunt for the opening {. Mixing styles in the source code can be confusing. Also using blank lines to break up the code helps in reading and finding problems.

The main goal here is to make the code as easy to read as you can. Mostly for your benefit, but also for other to read. The easier the code is to read it will improve the chance of a quicker response.

When it comes to compile time the compiler will ignore any white space, blank lines and comments and not make them part of the compiled program.

It is also a good idea to comment any code that is not easily understood.

I would like to see the rest of your program meaning all the code, other files that make up the program, the class to see what you have done. It is possible that there are other parts not seen that could be improved on or done differently, with less work.

In the end the more you show the more input you can receive.

Andy
Topic archived. No new replies allowed.