C++ map empty after inserting data. - c++

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.
}

Related

Writing a vector of objects to file and then reading it

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(...);

storing data in vectors using getline c++

my vector is not recording any data from my text file
the text file contains 106,187 words but for testing purposes i'm using the below set:
The text file format
bleeping
damned.
adj
blemishless
Without blemish; spotless.
adj
blendous
Pertaining to consisting of or containing blende.
adj
abaca
The Manila-hemp plant (Musa textilis); also its fiber. See Manila hemp under Manila.
n
abacinate
To blind by a red-hot metal plate held before the eyes.
v
abacination
The act of abacinating.
n
i need to get the words into the words vector and definitions into the definitions vector and then the types into the types vector. afterwards i need to search for a word and be able to get the meaning and the type. for this i was thinking of using a for loop would that be a good idea or is there any other way.
My Code
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
class Files {
private:
string word;
string definition;
string type;
string blank;
vector<string>words;
vector<string>definitions;
vector<string>types;
public:
void read();
//void intro();
void display(vector <string>& words);
};
void Files::display(vector <string> & words) {
std::cout << "The vector elements are : ";
for (int i = 0; i < words.size(); i++)
cout <<"running"<< words.at(i) << ' ';
}
void Files::read()
{
Files d;
int i = 0;
string e, t;
ifstream out("Text.txt");
string array[]{ d.word,d.definition,d.type,d.blank };
vector<string>words;
do
{
(getline(out, d.word, '\n'));
words.push_back(word);
getline(out, d.definition, '\n');
definitions.push_back(definition);
getline(out, d.type, '\n');
types.push_back(type);
getline(out, d.blank, '\n');
i++;
cout << "number of line " << i << ' ' << d.word << endl;
} while (!out.eof());
display(words);
}
int main()
{
Files d;
//intro();
d.read();
//d.display();
}
Pseudocode :
vector storage
string line
while getline(line): // this should read only empty lines.
assert line == ""
Element elem
if not getline(elem.word):
break // if it is the last line, stop
getline(elem.definition) // since a word is there, definition is also coming
getline(elem.type)
storage.append(elem.type)
You have some extra logic in there that is not really necessary. You should avoid creating an object of type Files inside the read function, it does not make much sense, you have those variables available as members but you're creating an object of the same type and using this object's local variables, which are the same to store the values you want to store in the member variables, it's mind twisting.
You should also use getline return values as a stop condition for your read loops.
You can also make string word, string definition, string type local to the read function.
An improved version of your function may look like this:
Live demo
void Files::read()
{
//make these local, it should work either way, it's just better design
string word;
string definition;
string type;
string blank;
int i = 0;
ifstream out("text.txt");
//use getline as a condition of the while loop instead of eof
while(getline(out, word, '\n') && getline(out, definition, '\n') && getline(out, type, '\n')){
words.push_back(word);
definitions.push_back(definition);
getline(out, blank, '\n');
types.push_back(type);
i++;
cout << "number of line " << i << " " << word << endl;
}
display(words);
}
Also note that the use of using namespace std is not recommended.

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.

cin missing first word of line

I've been having issues in a program involving cin.
My problem is that the first word of everything I input appears to be skipped, possibly because of the way the buffer is handled. I have seen similar posts regarding this but trying to apply their fixes to my code have so far failed. What is supposed to happen is the user inputs a name and that name gets stored in a text file with other entered data. However, it always drops the first word.
#include "string"
#include "stdafx.h"
string _name;
int main()
{
cout << "Choose a name" << endl;
getline(cin, _name);
cout << _name;
ofstream dat;
dat.open("data.txt");
dat << _name;
dat.close();
return 0;
}
This code is where the problem appears to be. I just can't get it to take the first word.
cin >> _name;
This reads the first word on the first line of input into _name.
getline(cin, _name);
This will read the rest of the line into _name. This overwrites the existing contents of name.
Because this overwrites the existing contents of _name, which contains the first word read, this ends up reading all except the first word of the line, as you described.
If you just want to read the entire line into _name, the only thing that needs to be done is to remove the cin >> _name.
If you want to read a name from cin, then your code should look something like this:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string _name;
cout << "Choose a name : ";
getline(cin, _name);
cout << _name << endl;
// Do something with _name - write to file etc..
// ..
}

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;
}