C++ Writing a vector of objects to file - c++

I have a vector of objects with quite a few variables (name, type, length etc) which I am trying to write to file.
vector <Boat> berths;
void Boat::write_boats()
{
ofstream file("records_file.txt");
for (Boat b : berths)
{
file << owner_name << "; " << boat_name << "; " << type << "; " << length << "; " << draft << '\n';
}
file.close();
}
void save_records()
{
for (unsigned int i = 1; i < berths.size(); i++)
{
berths[i].write_boats();
}
}
I call the save_records() function with a menu option that ends the application.
The output i get is:
1) If i register a boat object, close the app and go in the text file, I can see the object written twice.
2) If i register 2 objects and I go in the text file, only the last (second) object has been written to file, and it shows 3 times.
Now my questions are:
What causes the double output?
Why is only the last object written to file? I thought the loop would fix that but it didn't

One problem I can spot: "i = 1" in the loop should be "i = 0", because array indexes start from 0. The second: you iterate 'berths' array, so you will get N * N boats saved, if you have N boats in 'berths'.
The simple solution would be
void save_all()
{
ofstream file("records_file.txt");
for (Boat b : berths)
{
file << b.owner_name << "; " << b.boat_name << "; " << b.type << "; " << b.length << "; " << b.draft << '\n';
}
}
If you have to make 'owner_name', 'type' and the rest of the fields as private, then you would have to declare
void Boat::save(std::ofstream& f) const
{
file << owner_name << "; " << boat_name << "; " << type << "; " << length << "; " << draft << '\n';
}
and modify 'save_all' to
void save_all()
{
ofstream file("records_file.txt");
for (const Boat& b: berths)
b.save(f);
}

Every time ofstream file("records_file.txt"); is called, it created a new file and overwrite it, if you want to append in the file you have to open it by this way:
ofstream file("records_file.txt", ios::app);
See: http://www.cplusplus.com/doc/tutorial/files/

I guess you are using something like while(!bla.eof()), if so then it reaches the end of the buffer but it needs to go past it to raise the flag, so you have the same output twice at the end.

Related

sending struct to function C++

i have a program that asks for a number of gifts, and then has you input the description, price, and units for it.
i have a function used to display the details here inside it's own cpp file:
void display(const Gift&) {
cout << "Gift Details:" << endl;
cout << "Description: " << gifts.g_description << endl;
cout << "Price: " << gifts.g_price << endl;
cout << "Units: " << gifts.g_units << endl;
}
and here's where i try to call it through another cpp file:
for (int i = 1; i <= numberOfGifts; i++) {
cout << "Gift #" << i << endl;
display(gifts[i]);
cout << endl;
}
i can't seem to figure out how to have it display the first, second, third, and fourth values? it only displays the fourth values 4 times. would greatly appreciate some help
void display(const Gift&) accepts a reference to a Gift as a parameter, but without an identifier (a name) for the parameter the function cannot interact with the parameter.
Instead use void display(const Gift& gift) and then use gift in place of gifts in the function.
Future bug:
Arrays normally are valid between 0 and the array's dimension -1. In
for (int i = 1; i <= numberOfGifts; i++)
i ranges from 1 to numberOfGifts. At the very least this ignores the first, 0th, element of gifts and quite possibly will allow the program to access one past the end of the array. Skipping the first value is a waste of memory, but might be OK. Trying to access a value outside of the array is bad and the results very unpredictable.

C++ Csv file doesn't load to vector properly, it seems to know the objects are there but does not display them

I've been trying to read in a *.csv file to a vector, however it doesn't seem to work as such, it behaves that there are objects but doesn't display them and acts if they're empty.
This is the function to load the *.csv file.
void loadShoes()
{
fstream shoes;
shoes.open("shoes.txt", ios::in);
string shoeId;
string valueId;
while (getline(shoes, shoeId, ','))
{
getline(shoes, valueId, ',');
ShoeMap[shoeId] = valueId;
if (shoeId == "ShoeLaceStyle")
{
thefootwear.addShoe(ShoeMap);
};
}
}
This code is from the main that calls the function to load into a vector and then be displayed in a simple UI.
else if (userInput == 3)
{
for (int i = 0; i < thefootwear.vecNewShoe.size(); i++)
{
cout << i + 1 << " - " << thefootwear.vecNewShoe[i]->getShoeID() << "\n";
}
cout << "\nWhich Shoe would you like to view from the store?\n";
cin >> userInput1;
cout << "ShoeID - " << thefootwear.vecNewShoe[userInput1 - 1]->getShoeID() << "\n" << "ShoeName - "
<< thefootwear.vecNewShoe[userInput1 - 1]->getShoeName() << "\n" << "ShoeType - "
<< thefootwear.vecNewShoe[userInput1 - 1]->getShoeType() << "\n" << "ShoeSize - "
<< thefootwear.vecNewShoe[userInput1 - 1]->getShoeSize() << "\n" << "ShoeSoleStyle - "
<< thefootwear.vecNewShoe[userInput1 - 1]->getShoeSoleStyle() << "\n" << "ShoeColour - "
<< thefootwear.vecNewShoe[userInput1 - 1]->getShoeColour() << "\n" << "ShoeLaceStyle - "
<< thefootwear.vecNewShoe[userInput1 - 1]->getShoeLaceStyle() << "\n";
}
This is the method and function to add a new shoe with the required variables, in the Footwear class.
Shoe* Footwear::addShoe(map<string, string> ShoeMap)
{
Shoe* newShoe = new Shoe(ShoeMap["ShoeID"], ShoeMap["ShoeName"], ShoeMap["ShoeType"], ShoeMap["ShoeSize"], ShoeMap["ShoeSoleStyle"], ShoeMap["ShoeColour"], ShoeMap["ShoeLaceStyle"]);
vecNewShoe.push_back(newShoe);
return newShoe;
}
When I run the program, it displays the correct number of shoes currently in the *.csv file, however it fails to display their corresponding Shoe ID and I am, therefore, unable to access the variables related to the Shoes.
Speaking to your need rather than your specific question: Consider not writing your own custom CSV parser, but rather using a pre-existing C++ CSV parser library. There are several popular ones on GitHub for example.
This has the added benefit of being more robust against unexpected input contrivances (such as quoted numbers for example).

I am trying to write out the results of a process to a text file, the issue I have is with the alignment of the columns

I am sure someone somewhere has had this same issue but I have looked far and wide (including here on the stackoverflow) to find out how to properly align my columns in an output file. The following is the complete code I am using (for an event generator called Pythia 8 of which C++ is the primary language):
using namespace Pythia8;
int main()
{
Pythia pythia;
pythia.readString("Top:gg2ttbar = 1");
pythia.init(2212, 2212, 14000.);
ofstream myfile;
myfile.open("ttbar.txt");
for (int iEvent = 0; iEvent < 1; ++iEvent)
{
if (!pythia.next()) continue;
vector<double> part;
for (int i = 0; i < pythia.event.size(); ++i)
{
if (pythia.event[i].status() == 91) part.push_back(i);
}
myfile << "N = " << part.size() << endl;
for (int j = 0; j < (int(part.size()) - 1); ++j)
{
myfile << left << setw(4) << int(part[j]);
myfile << setw(4) << left << pythia.event[part[j]].name() << " "
<< right << pythia.event[part[j]].id() << " "
<< pythia.event[part[j]].px() << " " << pythia.event[part[j]].py()
<< " " << pythia.event[part[j]].pz() << " "
<< pythia.event[part[j]].m() << " " << pythia.event[part[j]].pT() << endl;
}
}
pythia.stat();
myfile.close();
return 0;
}
The issue occurs near the bottom where the loop that writes out the text file starts, as it is currently written in the above code, the first two columns are mashed together:
N = 665
1777pi- -211 1.19978 0.715507 32.7878 0.13957 1.39694
1779pi+ 211 -8.24173 6.07047 -31.6818 0.13957 10.2361
That is the first couple lines of the output (the program shows the line number where a certain particle is produced and relevant information about it like the name, mass...etc.). I cannot seem to format it so I don't have to use the inserted spaces that I put in by hand.
as it is currently written in the above code, the first two columns are mashed together
well, yes, you explicitly wrote the first two columns with no whitespace between them:
myfile << left << setw(4) << int(part[j]);
myfile << setw(4) << left << pythia.event[part[j]].name() << ...
If you want a general way to format this without worrying adding manual whitespace, split it into two steps:
create a vector<string> containing the columns for each line (you can just use an ostringstream to format each column individually)
write a function to take that vector and write it to an ostream, with spaces between.
std::copy(begin, end, std::ostream_iterator(myfile, " "));
will be sufficient if you just want a fixed number of spaces between each column

Data doesn't load into c++ vector correctly

I am trying to load a text file and import the contents into a vector of structs.
Here are my definitions
typedef struct
{
string pcName, pcUsername, pcPassword, pcMessage, pcAdvertisement; //I know that
//this is incorrect convention. It was originally a char*
}
ENTRY;
vector<ENTRY> entries;
fstream data;
Here is my display data function
void DisplayData()
{
std::cout << (int)(entries.size() / 5) <<" entries" << endl;
for(int i = 1; i <=(int)entries.size()/5; i++)
{
cout << endl << "Entry " << i << ":" << endl
<< "Name: " << entries[i].pcName << endl
<< "Username: " << entries[i].pcUsername << endl
<< "Password: " << entries[i].pcPassword << endl
<< "Message: " << entries[i].pcMessage << endl
<< "Advertisement: " << entries[i].pcAdvertisement << endl;
}
}
and here is my Load Data function
bool LoadData(const char* filepath)
{
std::string lineData ;
int linenumber = 1 ;
data.open(filepath, ios::in);
ENTRY entry_temp;
if(!data.is_open())
{
cerr << "Error loading file" << endl;
return false;
}
while(getline(data, lineData))
{
if(linenumber==1) {entry_temp.pcName = lineData;}
else if(linenumber==2) {entry_temp.pcUsername = lineData;}
else if(linenumber==3) {entry_temp.pcPassword = lineData;}
else if(linenumber==4) {entry_temp.pcMessage = lineData;}
else if(linenumber==5) {entry_temp.pcAdvertisement = lineData;}
entries.push_back(entry_temp);
if(linenumber == 5)
{
linenumber = 0;
}
linenumber++;
}
data.close();
puts("Database Loaded");
return true;
}
Here is the text file I am loading:
Name1
Username1
Password1
Message1
Ad1
And here is the result of the display data function after calling load data:
1 entries
Entry 1:
Name: Name1
Username Username1
Password:
Message:
Advertisement:
As you can see, the first two load but the last three don't. When I did this with an array instead of a vector, it worked fine so I don't know what I'm doing wrong. Thanks.
I suggest that you read each line directly into the data field where it goes:
getline(data, entry_temp.pcName);
getline(data, entry_temp.pcUsername);
getline(data, entry_temp.pcPassword);
getline(data, entry_temp.pcMessage);
getline(data, entry_temp.pcAdvertisement);
entries.push_back(entry_temp);
This makes your intent much clearer than your current while loop. It also creates a single entry for all 4 input lines rather than one for each input line (with the other three blank). Now you can read several "entries" by using a while loop that checks if you have reached the end of the file.
Doing this will also make printing out the data much easier since the vector will have exactly the number of entries rather than five times as many as you expect (which also eats up a lot more memory than you need to).
Your DisplayData function is a little weird, and so is your LoadData.
Your LoadData pushes back a new copy of the current ENTRIES entry with every line. Your DisplayData starts at 1 (which is not the beginning of any vector or array), and iterates only up to the 1/5th entry of the entire vector.
This needs a heavy rework.
First, the size() member of any standard container returns the number of elements that it contains, and will not take the number of fields in a contained struct into account.
For future reference, you'll want to post your question in a complete, standalone example that we can immediately compile to help. (see http://sscce.org/)
Try this modified data, which runs correctly, and see if you can tell what is being done differently:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
typedef struct
{
string pcName, pcUsername, pcPassword, pcMessage, pcAdvertisement;
}
ENTRY;
vector<ENTRY> entries;
fstream data;
bool LoadData(const char* filepath)
{
std::string lineData ;
int linenumber = 1 ;
data.open(filepath, ios::in);
ENTRY entry_temp;
if(!data.is_open())
{
cerr << "Error loading file" << endl;
return false;
}
while(getline(data, lineData))
{
if(linenumber==1) {entry_temp.pcName = lineData;}
else if(linenumber==2) {entry_temp.pcUsername = lineData;}
else if(linenumber==3) {entry_temp.pcPassword = lineData;}
else if(linenumber==4) {entry_temp.pcMessage = lineData;}
else if(linenumber==5) {entry_temp.pcAdvertisement = lineData;}
if(linenumber == 5)
{
entries.push_back(entry_temp);
linenumber = 0;
}
linenumber++;
}
data.close();
puts("Database Loaded");
return true;
}
void DisplayData()
{
std::cout << entries.size() <<" entries" << endl;
for(int i = 0; i < entries.size(); i++)
{
cout << endl << "Entry " << i << ":" << endl
<< "Name: " << entries[i].pcName << endl
<< "Username: " << entries[i].pcUsername << endl
<< "Password: " << entries[i].pcPassword << endl
<< "Message: " << entries[i].pcMessage << endl
<< "Advertisement: " << entries[i].pcAdvertisement << endl;
}
}
int main()
{
LoadData("/tmp/testdata");
DisplayData();
return (0);
}
While I think #code-guru has the right idea, I'd take the same idea just a little further, and make your code work a little more closely with the standard library. I'd do that by reading a data item with a stream extractor, and displaying it with stream inserter. So, the extractor would look something like this:
std::istream &operator>>(std::istream &is, ENTRY &e) {
getline(is, e.pcName);
getline(is, e.pcUsername);
getline(is, e.pcPassword);
getline(is, e.pcMessage);
getline(is, e.pcAdvertisement);
return is;
}
..and the inserter would look something like this:
std::ostream &operator<<(std::ostream &os, ENTRY const &e) {
os << e.pcName << "\n";
os << e.pcUsername << "\n";
os << e.pcPassword << "\n";
os << e.pcMessage << "\n";
os << e.pcAdvertisement << "\n";
return os;
}
With those in place, loading and displaying the data becomes fairly straightforward.
Load the data:
std::ifstream in("yourfile.txt");
std::vector<ENTRY> data((std::istream_iterator<ENTRY>(in)),
std::istream_iterator<ENTRY>());
Display the data:
for (auto const & e: data)
std::cout << e << "\n";
For the moment, I haven't tried to duplicate the format you were using to display the data -- presumably the modifications for that should be fairly obvious.

Overwrite a line with ofstream C++

I am doing a little game and I am saving the player details in a txt file.
Example of that txt file:
Eric 13 8 10 10 30 10 10 50 0 0 0 0
William 1 0 10 30 30 10 10 50 0 0 0 0
John 1 0 10 30 30 10 10 50 0 0 0 0
This is what I had in mind: when the player chooses to save the game while playing, the save_game function should check if there is already any saved data. If there is, instead of appending the data to the end of the txt, it should overwrite that specific line.
Here is my current function:
// SAVE GAME
void save_game(Player player)
{
ofstream coutfile (SaveDestiny, ios::app);
if (coutfile.is_open()) // if it opens correctly
{
// Now checking if the name already exists
string imported_name;
ifstream cinfile (SaveDestiny); // opens file that contains the saved games
cinfile >> imported_name; // getting first element of file
bool j = 0; // j = 0 while the strings don't match. j = 1 when the string was found
while (cinfile >> imported_name) // while the end of file is not reached
{
if (player.name.compare(imported_name) == 0) // if the strings are the same, overwrite data
{
j = 1;
coutfile << " \r" << endl;
break;
}
else // if the strings are different, keep reading
{
cinfile >> imported_name;
}
}
// Continuing...
coutfile << player.name << " " << player.level << " " << player.exp << " " << player.max_exp << " "
<< player.hp << " " << player.max_hp << " " << player.mp << " " << player.max_mp << " "
<< player.gold << " " << player.weapon << " " << player.shield << " " << player.heal_spell << " "
<< player.attack_spell << endl;
}
else
{
ofstream coutfile (SaveDestiny, ios::app);
coutfile << "test";
cout << "Unable to open file";
cin.get();
}
draw_rectangle(37,8,72,14,15); // white limits
draw_rectangle(39,9,70,13,9); // blue background
cor(9,15);
gotoxy(50,10);
cout << "GAME SAVED!";
gotoxy(41,12);
cor(9,14);
cout << "Press <Enter> to continue... ";
cin.get();
}
On most modern filesystems files are not "line-based" (or "record-based") they are character-based so you can't "overwrite a line". The old line might be 20 characters long and the new one would be 24 characters, in which case it would overwrite the old line and the first 4 characters of the next line. To make this work you would have to "push" everything after the line later in the file, which isn't possible with C++ (or C) IO facilities.
One option would be to write all lines with a fixed length, say 50 characters, so that overwriting the 3rd line involves replacing characters 100 to 149, even if the line only actually needs 24 characters.
Another option would be to keep the file in memory in a record-based form and write out the entire file every time you change it (or at least write out the new line and all lines that come after it)
Ok I've managed to get around the problem and now it's working brilliantly! :D
First, the function checks if the player name already is on the txt. I created a enable variable j. When j=1, the name exists and the data needs to be overwritten! When j=0, the function will append the data to the txt right away.
Ok, let's say j=1. The function determines the number of lines in txt. It then creates a vector with two vectors inside: the name, and the game variables.
After that, the function deletes the previouscontent of txt file. And writes the content of the vector to the txt, except the data that needs to be overwritten (it will skip writing that part to the txt), because at the end of the function, that new data will be written. :D I hope I made myself clear enough. Sorry if someone doesn't understand what I wrote...
Here is my new save_game function:
// SAVE GAME
void save_game(Player player)
{
ofstream coutfile (SaveDestiny, ios::app);
if (coutfile.is_open()) // if it opens correctly
{
string imported_name;
ifstream cinfile (SaveDestiny); // opens file that contains the saved games
bool j = 0;
// Now checking if the name already exists
while (cinfile >> imported_name) // while the end of file is not reached
{
if (player.name.compare(imported_name) == 0) // if the strings are the same, overwrite data
{
j = 1; // enable overwrite
break;
}
// if the strings are different, keep reading
}
// at this point: j = 0 to append to end. j = 1 to overwrite.
// Overwriting data
if (j == 1)
{
ifstream cinfile (SaveDestiny);
// now determining the size of the vector (number of lines in txt)
int line_numbers = 0;
string line;
while (getline(cinfile, line))
{
line_numbers++;
}
cinfile.close(); // closing
ifstream cinfile2 (SaveDestiny); // reopening to read from the beginning
// now creating the vector with the saves
vector<vector<string>> temp_saves(line_numbers, vector<string>(2));
string name2;
string values;
for (unsigned int x = 0; x < temp_saves.size(); x++)
{
cinfile2 >> name2;
getline(cinfile2, values);
temp_saves[x][0] = name2;
temp_saves[x][1] = values;
}
coutfile.close(); // closing output file
ofstream coutfile2 (SaveDestiny); // reopening in overwrite mode
// delete all saves.txt, copying vector content to txt (except the one we want to overwrite)
for (unsigned int x = 0; x < temp_saves.size(); x++)
{
if ( temp_saves[x][0].compare(player.name) != 0)
{
coutfile2 << temp_saves[x][0] << temp_saves[x][1] << endl;
}
}
coutfile2.close(); // closing output file
}
// Appending new data...
ofstream coutfile3 (SaveDestiny, ios::app); // reopening in append mode
coutfile3 << player.name << " " << player.level << " " << player.exp << " " << player.max_exp << " "
<< player.hp << " " << player.max_hp << " " << player.mp << " " << player.max_mp << " "
<< player.gold << " " << player.weapon << " " << player.shield << " " << player.heal_spell << " "
<< player.attack_spell << endl;
}
else
{
ofstream coutfile (SaveDestiny, ios::app);
cout << "Unable to open file";
cin.get();
}
draw_rectangle(37,8,72,14,15); // white limits
draw_rectangle(39,9,70,13,9); // blue background
cor(9,15);
gotoxy(50,10);
cout << "GAME SAVED!";
gotoxy(41,12);
cor(9,14);
cout << "Press <Enter> to continue... ";
cin.get();
}