Writing a vector of objects to file and then reading it - c++

I'm new to C++ and stackoverflow so forgive me any mistakes in my post ;). I need to create a code, which allows me to fill new objects with data from std::cin and export these objects to binary file later. Also, I need to import objects exported to file at some point. Objects represent users with standard user information like username, ID, lvl etc.
#include <vector>
#include <string>
#include <iostream>
class User {
std::string username;
unsigned int ID, lvl;
public:
User(std::string un, int uID, int ulvl) {
username = un;
ID = uID;
lvl = ulvl;
}
};
int main() {
std::string u_name;
int u_ID,u_lvl;
bool finish = false;
char choice;
std::vector<User> test_user_vec;
do {
std::cout << "Enter username: ";
std::cin >> u_name;
std::cout << "Enter ID: ";
std::cin >> u_ID;
std::cout << "Enter lvl: ";
std::cin >> u_lvl;
test_user_vec.push_back(User(u_name, u_ID, u_lvl));
std::cout << "Do you want to add another user? (y/n)?";
choice = getch();
if (choice == 'y') finish = true;
} while (!finish);
return 0;
}
I assume that test_user_vec stores every object I created while my program is running. My problem occurs when I want to export that vector to file. The purpose of this action is to store objects' data even after my program terminates and import all the data when I run my program again.
I was trying to solve this problem on my own, nothing really came to my mind. While I was looking for some info i found something like this:
#include <fstream>
#include <vector>
#include <string>
int main()
{
std::vector<std::string> v{ "one", "two", "three" };
std::ofstream outFile("my_file.txt");
// the important part
for (const auto &e : v) outFile << e << "\n";
}
I've tested it with <string> and <int> vectors and my variables. It's good until I try to export <object>vector.
Also i found another solution and tried to do something with it on another test code:
class Test {
public:
int number;
float number2;
};
int main(){
Test test1;
test1.number = 122;
test1.number2=12;
std::fstream testfile("test1.bin", std::ios::out | std::ios::binary);
testfile.write((char*)&test1, sizeof(test1));
testfile.close();
//ater writing an object with variables i commented this section
//then uncommented this section and run the program again
std::fstream testfile2("test1.bin", std::ios::in);
testfile2.read((char*)&test1, sizeof(test1));
std::cout << test1.number;
testfile2.close();
return 0;
}
Again, it works, i can read test1.number until I want to use vector of objects, not a single object. With vector of objects my cout printed some random values like 11314123e-03.
I was trying to somehow combine these 2 solutions, but nothing worked out. I would like to have a binary file, because i heard it's faster and has any data protection (i can't just open it in notepad and read the data) I'm new to c++, there is a great chance of me trying to do it reeeeeealy inefficient way, so pls help :D.

Data member getter functions can be added to the User class and used in fstream output operations. This should provide the general idea:
std::string userName;
for (const auto &u : v)
{
outFile.write(u.GetID(), sizeof(int));
outFile.write(u.GetLvl(), sizeof(int));
userName = u.GetName();
outFile.write(username.length(), sizeof(size_t));
outFile.write(userName.data(), username.length());
}
For userName, the length is written to precede the userName string data in the file so that the file can be parsed when read. The binary encoding/convention is designer's decision as there are several options. Another option would be to encode the entire object as a null-terminated string, although this would generally be less size efficient except for the userName string itself.
Note: test_user_vec.push_back(User(u_name, u_ID, u_lvl)); is creating temporary User objects on the stack. As #drescherjm and #RaymondChen pointed out, that is OK, but this is a better alternative: test_user_vec.emplace_back(...);

Related

Using a function to open a file and then having other functions utilize that file afterwards?

I have a computer science assignment which requires me to have a separate function just to open the file, and then another function which will then process the data in that file and then some others to do some operations with that data. Anyways, I'm having trouble in how to be able to let other functions use that opened file. References with '&' or'*' are confusing me and I'm unsure if I have to use one or not, of course, though I'm pretty sure I'll have to pass at least something to the next function. The main intent when dealing with the file is to open it(openFile) and then have another function(getData) to sort the data into two different arrays. One for the names, and one for the amounts next to them. The file would be written as:
Johnson 6000
Brown 5000
Miller 4000
Duffy 2500
Robson 1800
My code is as follows:
'''
#include <iostream>
#include <string>
#include <iomanip>
#include <fstream>
using namespace std;
void openFile();
void getData();
void computePercentages();
void sortVotes();
void display();
void displayWinner();
int main() {
openFile();
getData();
return 0;
}
void openFile(){
string fileName;
cout << "Enter the name of the file to open: ";
cin >> fileName;
ifstream file;
file.open(fileName.c_str());
}
void getData(){
int count = 0;
while(!file.eof()){
string names[count];
int votes[count];
cin >> names[count];
cin >> votes[count];
count ++;
}
}
'''
One way is to have openFile return the file stream object, then pass it to getData.
ifstream openFile()
{
string fileName;
cout << "Enter the name of the file to open: ";
cin >> fileName;
ifstream file(fileName);
return file;
}
void getData(ifstream &file)
{
int count = 0;
while(file){
string names[count];
int votes[count];
cin >> names[count];
cin >> votes[count];
count ++;
}
}
int main()
{
ifstream file = openFile();
if (file)
{
getData(file);
}
}
Note that this answer does not fix other issues in your code. For example, in getData you're using variable-length arrays which are non-standard and won't work on all compilers, and those arrays are constructed and destroyed each time through the while loop.
There are many ways to do it..
Here is a simple way.. using global variables.
I made ifstream file; as global..
This is not good way.. but simple..
#include <iostream>
#include <string>
#include <iomanip>
#include <fstream>
using namespace std;
void openFile();
void getData();
void computePercentages();
void sortVotes();
void display();
void displayWinner();
ifstream file;
int main() {
openFile();
getData();
return 0;
}
void openFile(){
string fileName;
cout << "Enter the name of the file to open: ";
cin >> fileName;
file.open(fileName.c_str());
}
void getData(){
int count = 0;
while(!file.eof()){
string names[count];
int votes[count];
cin >> names[count];
cin >> votes[count];
count ++;
}
}
Your getData() function has some problems:
void getData(){
int count = 0;
while(!file.eof()){ // this is almost never the correct check
string names[count]; // you declare new VLA:s (non-standard) every iteration
int votes[count]; // -"-
cin >> names[count]; // and you put a value in it out of bounds.
cin >> votes[count]; // -"-
count ++;
} // both arrays are destroyed here
}
file.eof() does not return true until you've tried to read beyond the end of the file. If you've read the last value, it will not be set. Only when you try next time will it be set.
The arrays you declare inside the while loop will be destroyed at the end of the loop. After the loop is finished, you have no arrays.
When you declare an array of count elements, you can access those elements using 0 to count-1 inclusive. You access element count which is out of bounds so your program has undefined behaviour.
VLA:s (variable length arrays) does not exist in standard C++ (but does as an extension in some compilers). If you know exactly how many elements you need to store, you can use std::array instead, but in this case, use a std::vector.
It uses a global file variable (that doesn't even exist). Try to stay away from global variables if you can.
The records in your data file should be kept together instead of putting each column in a separate array. A simple placeholder for each record in your file could look like this:
struct record {
std::string name{};
int vote{};
};
With that, you only need one array (or std::vector).
std::vector<record> records;
It'd also be good if one could extract one complete record from a stream using the same >> operator as you used for int and std::string. Like this:
record temp; // declare a variable using your own type, "record"
while(file >> temp) { // read records until no more can be read
records.push_back(temp) // add one record to records
}
A function to read one record from an istream, like an ifstream:
std::istream& operator>>(std::istream& is, record& r) {
// You may want to use getline here instead in case the names contain spaces.
return is >> r.name >> r.vote; // extract name and vote from is and return is
}
The function takes both parameters (is and r) by reference. That means that whatever is done to the parameters inside the function affects the variables that were used to call the function. file >> temp results in a call to the above function where is is a reference to file and r is a reference to temp.
For openFile() I'd suggest:
std::ifstream openFile(const std::string& fileName) { // return the ifstream by value
return std::ifstream{fileName};
}
Getting the filename from the user doesn't have anything to do with opening the file, so get the filename before calling the function. The above function lets you call openFile() and get an ifstream in return:
std::ifstream file = openFile(fileName);
You can now call getData() using file, but it needs to be able to receive it. Standard stream objects can't be copied (passed by value), but we don't need to. Just make getData() receive a reference to the stream. I'd make it an istream instead of an ifstream to be able to read from any istream decendant:
std::vector<record> getData(std::istream& is) {
// create a vector, read data from "is" and put it in vector and return vector when done
}
When all is pieced together, you could have a main() looking something like this:
int main() {
std::vector<record> records;
std::cout << "Enter the name of the file to open: ";
// use getline since a filename may contain spaces
if(std::string fileName; std::getline(std::cin, fileName)) {
// if "file" is in a good state after openFile(), call getData()
if(std::ifstream file = openFile(fileName)) {
records = getData(file);
} // "file" is automatically closed when it goes out of scope
}
// print what you collected
for(const record& r : records) {
std::cout << r.name << "\t" << r.vote << "\n";
}
}
The above uses If Statements with Initializer which is a C++17 feature to help create a narrow scope for variables.

C++ map empty after inserting data.

Firstly, Happy new year to those who come across my question.
I'm currently learning C++ and I have a class project to complete.
In a nutshell, my code (so far) is supposed to instantiate student objects with details read from a txt file (name, reg-number, and a map of ) and add the student to a list.
I then read a second txt file (consisting of reg-number, course code and mark) and check if there is a match of student reg numbers between whats read and the list.
if there is a match, I should insert the marks read from the txt file into the map (part of the student object), such that afterwards, each student has a map containing the courses taken and marks achieved
I seem to be creating the list fine, then I use a stringstream to read the second file and loop through the list to compare reg-numbers.
if there is a match I then call the add mark method to add marks to the map.
Here's the thing.. If after I completed the map inserting, I loop and print a map of a student, the map is empty. for all students. To confirm this I used map.size().
I have tried many ways to understand and rectify the issue but it seems i'm missing the point of something. Instinct tells me that the add mark method is copying a reference to the variable passed, which is then destroyed by the stringstream in the main method, thus showing no data in the map. unfortunately, I can't change any code within the header files, only implement what's declared.
after reading the std library docs for strings, maps etc, and attempting numerous ways of correcting the behaviour, I'm at a loss.
Any advice would be greatly appreciated so I can continue with the project and better understand what is happening. I have added the files below. there is also a Person base class but I havnt changed this as it consists of only a setter and getter.
Many thanks in advance.
student Header:
#ifndef _STUDENT_H_
#define _STUDENT_H_
#include <string>
#include <map>
#include <stdexcept>
#include "Person.h"
using namespace std;
class NoMarkException: public exception
{
};
class Student: public Person
{ public:
// constructor should initialise name and registration number using arguments
// and initialise marks map to be empty
Student(const string &name, int regNo);
// method to return registration number
int getRegNo() const;
// method to add the mark to the map
// if a mark for the module is already present it should be overwritten
void addMark(const string& module, float mark);
// method to retrieve the mark for a module
// should throw NoMarkException if student has no mark for that module
float getMark(const string &module) const throw (NoMarkException);
private:
int regNo;
map<string, float> marks; // keys are modules, values are marks in range 0.0 to 100.0
// friend function to output details of student to stream
// should output name, regno, and minimum, maximum and average marks on a single line
// if the student has no marks "has no marks" should be output instead of the marks
friend ostream& operator<<(ostream &str, const Student &s);
};
#endif
Student.cpp file:
#include <iostream>
#include "Student.h"
#include "Person.h"
using namespace std;
//constructor makes a student object and initialises the map; marks.
Student::Student(const string &name, int regNo) : Person(name) {
this->name = name;
this->regNo = regNo;
map<string, float> marks;
}
//implemented from the header file. Returns the registration number.
int Student::getRegNo() const {
return regNo;
}
// implemented as per header file request. adds marks to the map. if a mark exists, then it is overwritten.
void Student::addMark(const string &module, float mark) {
marks[module] = mark;
cout << "added: " << marks[module]<< endl;
}
//used to find marks in a map.
float Student::getMark(const string &module) const throw (NoMarkException) {
auto search = marks.find(module);
//line to test the map size after using addMark.
cout << "size after inputted values: "<< marks.size();
return marks[module];
}
main.cpp file
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <list>
#include "Student.h"
using namespace std;
//main method to obtain a file name from a user and read the file.
//method passes each .. to student.cpp
int main() {
//setting up variables required
string fileName;
const string fileEnd = ".txt";
string line;
string line2;
ifstream file;
int reg;
int reg2;
string studName;
string lastName;
float mark;
string module;
list<Student> listOfStudents;
cout << "Please enter a file name to access..." << std::endl;
cin >> fileName;
fileName += fileEnd;
// opening file an reading its contents. values are prepared and sent to the Student constructor. Fills a list
// with student objects created with variables read from the studs.txt file.
//checks file is found and exits the program if not
file.open(fileName);
if (!file) {
cerr << "Unable to open file " << fileName << endl;
exit(1);
}
while (getline (file, line)) {
stringstream stream (line);
stream >> reg >> studName >> lastName;
studName += (' ' + lastName);
cout << "Student: " << studName << " has been created." << endl;
listOfStudents.push_front(Student(studName, reg));
}
file.close();
cout << "The list of students has been created :)" << endl << endl;
cout << "Please enter the name of the next file to open"<< endl;
cout << listOfStudents.size()<<endl;
// opening second file. If file not found, exit with an error code.
// otherwise read each line, separate the three words into variables, then loop through the previously created list
//and look for a match of regNo. if true then call the addMark method to add the module and mark to the student's map.
cin >> fileName;
fileName += fileEnd;
file.open(fileName);
if (!file) {
cerr << "Unable to open file " << fileName << endl;
exit(1);
}
while(getline(file, line))
{
istringstream line_stream(line);
line_stream >> reg2 >> module >> mark;
for(Student stud : listOfStudents){
if(stud.getRegNo() == reg2){//out << "Match reg2: " << reg2 << " with stud: " << stud.getName() <<stud.getRegNo()<< endl;
stud.addMark(module,mark); }
}
}
//testing the get mark method of student class. with a module that is in the map. expecting a result
for(Student s :listOfStudents){
cout << s.getMark("CE151") << endl;
}
return 0;
}
You create a temporary copy of Student each time you're going to addMark, and discard it afterwards. You need not to copy it, but use reference, like this:
for (auto& stud: listOfStudents) /* stud.addMark */
It would also be a good idea to avoid copying Student when you print the results:
for (const auto& stud: listOfStudents) /* stud.getMark */
Aaaand one more thing:
float getMark(const string &module) const throw (NoMarkException);
Dynamic exception specification is deprecated in C++11 and removed in later standards. It is considered to be a bad practice and should be avoided.
The answer by user: grungegurunge is pretty much the one you are looking for. However, after looking at your class. I noticed that in the private section for the member variables you have declared map<string, float> marks which is okay. Yet when I look at your class's constructor after you set the name and regNo it appears that you are declaring another map<string, float> named marks where this one is local to the constructor only and does nothing. You create local memory on the stack with automatic storage, never use it, then it gets destroyed after the object goes out of scope; it is unnecessary to have it declared here.
//constructor makes a student object and initialises the map; marks.
Student::Student(const string &name, int regNo) : Person(name) {
this->name = name;
this->regNo = regNo;
// map<string, float> marks; // this line is not needed.
}

Incorrect addition from a file

if (infile.is_open())
{
int count = 0;
while (infile)
{
string author, ratings;
getline(infile, author);
if (author != "")
{
getline(infile, ratings);
// TODO: Create new User object
User newuser(author, ratings);
// TODO: Add new User object to vector
userList.push_back(newuser);
count++;
}
}
cout << count << " users read in. Closing user file." << endl;
The output for this that I am getting is that 86 users were read in from the text file. the correct output is supposed to be 32. I think that it is because I am using a while loop but I am not fully sure.
Your condition should be something like
while (getline(author, infile) && getline(ratings, infile)) {
// validate input, then process it
}
Then the if (infile.open()) becomes trivial. There is a '}' missing in the code you posted, which makes it hard to really tell where your counting error is coming from, or maybe that's just the reason, incrementing your count in the wrong place. Please make sure your examples are complete and possibly even compile.
A little tip, you can just write
userList.push_back(User(author, ratings));
EDIT:
I created this minimal test code (for you) and tested it on the following file, resulting in the following output. Can you confirm? Please note: The current program doesn't accept newlines in your file, e.g. for grouping various users, however, this is a feature easily added, once the basic program works.
Code:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
struct User {
string author, ratings;
User(string auth, string rat)
: author(auth), ratings(rat) {}
};
int main()
{
ifstream ist("test.txt");
if (!ist) {
cout << "Could not open file test.txt\n";
return 1;
}
vector<User> userList;
string author, ratings;
size_t count = 0;
while (getline(ist, author) && getline(ist, ratings)) {
if (author != "" && ratings != "") {
userList.push_back(User(author, ratings));
++count; // in this case, count++ is equivalent
}
}
cout << count << " users read in. Closing user file.\n";
}
The file test.txt
foo
bar
foobar
lalilu
myTotalUsersAre
3
Output:
3 users read in. Closing user file.

How can you initialize an object from a variables value?

I would like to generate the name of a soon to be initialized object by getting the user input. Is this possible? Is there a way to name an object based on a variables value?
I know in the code below the "test x" doesn't work and I know why... but is there a way to get this to work? I have been trying to figure it out for 2 days now. The closest thing I came to was maybe stringstreams... or even worse... it isn't possible? Any help?
#include <iostream>
using namespace std;
#include <string>
class test
{
public:
string wtf;
};
int main()
{
test y; // showing normal initialize
y.wtf = "a"; // assign value
string x;
cin >> x; // initialize string and get input
test x // trying to initialize object based on input
x.wtf = "a"; // then assign a value
cout << y.wtf; // printing both to test
cout << x.wtf;
return 0;
}
My intent is to have a single array that holds employee numbers (concatenated with a an "emp" or something in the beginning) to initiate objects for each employee. So the user would input an employee number, say 1234, and I would make a string that added emp + 1234 and come out with "emp1234"... which I would then initiate an object like "test emp1234" which would have a bunch of difference variables associated with it, inside the class.
I may be looking at this all wrong... and probably explained it fairly crappy. I am obviously new to this, but I could use any help possible.
I would do it like this:
#include <vector>
#include <string>
#include <iostream>
#include <stdexcept>
test make_test()
{
std::string s;
if (!(std::cin >> s))
{
throw std::runtime_error("Premature end of input");
}
return { "emp" + s };
}
int main()
{
std::vector<test> v;
for (int i = 0; i != 10; ++i)
{
v.push_back(make_test());
std::cout << "Added employee '" << v.back().wtf << "'\n";
}
}
This would fill a vector with ten employee records.
No, you can't create variable names at run-time simply because names do not exist at run-time. They are static constructs that are only useful during compilation.
However, it really sounds like you just want a std::map. You can use this to map from integers (employee number) to an test object:
std::map<int, test> employees;
int employeeNumber;
std::cin >> employeeNumber;
employees[employeeNumber]; // Will default construct a test object mapped to employeeNumber
// Alternatively:
test t( /* some constructor args */ );
employees[employeeNumber] = t;
Then, if you want to access the test object with employee number 1234, for example, you could simply do:
employees[1234]

Unable to take input from a file containing string variables

I decided to make a phonebook in c++ and decided to take input of names, address and number from a file.
So I made a class called contact and declared the public variables name, address and number.
Using a constructor I initialized these to name="noname"(string), number=0(int), address="no address"(string)
Now my body of main goes as:
int main(){
contact *d;
d= new contact[200];
string name,add;
int choice,modchoice;//Variable for switch statement
int phno,phno1;
int i=0;
int initsize=0, i1=0;//i is declared as a static int variable
bool flag=false,flag_no_blank=false;
//TAKE DATA FROM FILES.....
//We create 3 files names, phone numbers, Address and then abstract the data from these files first!
fstream f1;
fstream f2;
fstream f3;
string file_input_name;
string file_input_address;
int file_input_number;
f1.open("./names");
while(f1>>file_input_name){
d[i].name=file_input_name;
i++;
}
initsize=i;
f2.open("./numbers");
while(f2>>file_input_name){
d[i1].phonenumber=file_input_number;
i1++;
}
f3.open("./address");
while(f3>>file_input_address){
d[i1].address=file_input_address;
i1++;
}
now when I later search for a particular entry by name, the name is displayed correctly but the phoneumber is returned as a garbage value and address as "Noaddress"
I dont understand why this is happening...
In case u want to look at entire code, do let me know....
This is how i search for a particular entry which returns the name if matched but returns garbage for phone number....
cout<<"\nEnter the name";//Here it is assumed that no two contacts can have same contact number or address but may have the same name.
cin>>name;
int k=0,val;
cout<<"\n\nSearching.........\n\n";
for(int j=0;j<=i;j++){
if(d[j].name==name){
k++;
cout<<k
<<".\t"
<<d[j].name
<<"\t"<<d[j].phonenumber
<<"\t"<<d[j].address
<<"\n\n";
val=j;
}
}
Thanks in advance
When your reading the file with the phone numbers
f2.open("./numbers");
while(f2>>file_input_name){
d[i1].phonenumber=file_input_number;
i1++;
}
You store the phone number in the string file_input_name but then you use a different var, file_input_number to store the information in the array d;
Hey guys I figured out the problem....
the problem is that i1 should be set to 0 after the second loop
and that the file taking input numbers should be f2.open("numbers") and not names....silly mistake!!
Since you are using C++, and not C, you should take advantage of the things that come with the language. Don't use arrays to store your data, use a std::vector. That way you don't have to remember how many things you have already put into the vector, because you can always ask the vector to tell you the size().
If I had to read in the three files I would go like this:
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <algorithm>
using std::cin;
using std::cout;
using std::fstream;
using std::string;
using std::vector;
class contact {
public:
string name;
string address;
int phone;
};
void print_contact(const contact &c) {
cout << "name " << c.name << " address " << c.address << " phone " << c.phone << "\n";
}
int main(int argc, char **argv)
{
vector<contact> contacts;
string name;
string address;
int phone;
fstream f1("d:\\names.txt");
fstream f2("d:\\phones.txt");
fstream f3("d:\\addresses.txt");
// note that I am using getline() here.
while (getline(f1, name) && f2 >> phone && getline(f3, address)) {
contact c;
c.name = name;
c.address = address;
c.phone = phone;
contacts.push_back(c);
}
for_each(contacts.begin(), contacts.end(), print_contact);
// for the Windows console window
cout << "Press return to continue ...";
string s;
getline(cin, s);
return 0;
}