So I have a custom class 'Book' that has a bunch of member variables, amongst which are a vector of another custom class called 'Review' and a pointer to point at that vector as I need to pass that around through function calls in a driver program. The driver program reads the details of the each book (such as title, author, publish date etc.) from a text file and inserts into a temporary 'Book' object which it then adds onto a vector of Books maintained by the driver program. Here is the code for reading from the file:
ifstream file("books.txt");
string line;
if(file.is_open())
{
while(!file.eof())
{
Book buffBook;
getline(file, line);
buffBook.setTitle(line);
getline(file, line);
buffBook.setAuthor(line);
getline(file, line);
buffBook.setPubDate(line);
getline(file, line);
buffBook.setIsbn(line);
getline(file, line);
buffBook.setCategory(line);
getline(file, line);
buffBook.setFormat(line);
getline(file, line);
buffBook.setSynopsis(line);
vectBooks.push_back(buffBook);
}
}
else
cout<<"File not found(1)!"<<endl;
file.close();
This is running inside a int main() function.
One of the functions of the driver program is to add a review, which takes in data from the user and inserts it into a temporary 'Review' object. That object is then passed onto to be inserted in to the vector of reviews for the corresponding book. Here is the code for the addReview() function:
void addReview()
{
string name = "";
string title;
Book rTemp;
cin.ignore();
cout<<"Which book would you like to rate (Title)?: ";
getline(cin, name);
name = toLow(name);
Review r;
string re, user;
int ra;
cout<<"Username (Full Name): ";
getline(cin, user);
string fname = user.substr(0, user.find_first_of(' '));
string lname = user.substr( user.find_first_of(' ') + 1, user.size());
r.setUsrFName(fname);
r.setUsrLName(lname);
cout<<"Enter rating (1-5):";
cin>>ra;
r.setRating(ra);
cout<<"Enter a short textual review: ";
cin.ignore();
getline(cin, re);
r.setReview(re);
for(unsigned int i = 0; i < vectBooks.size(); i++)
{
title = toLow(vectBooks[i].getTitle());
if(title.find(name) != string::npos)
{
vectBooks[i].getReviews()->push_back(r);
}
}
}
Now the problem is if i add a review, it adds it for all the books. In other words, when I fetch the book info for any book, the review shows on all the books. I assume this is a problem with the pointer as it seems like all the reviews are getting stored in the same vector. I am not sure where I am messing up but I have a feeling it's with the pointer some where. Any help is appreciated.
Thank You
UPDATE
The point to the title of this problem is that I am doing the assignment of the pointer to the vector of Reviews in the constructor of the Book class, of which those 2 are member variables. Code as follows for constructor:
Book::Book()
{
pointRev = &vectReviews;
}
UPDATE 2
Here is the code for the Book Class and supporting classes:
book.h
#ifndef BOOK_H_
#define BOOK_H_
#include <string>
#include <iostream>
#include <vector>
#include "review.h"
using namespace std;
class Book
{
private:
string title;
string author;
string pubDate;
string isbn;
string category;
string format;
string synopsis;
vector<Review> vectReviews;
vector<Review>* pointRev;
public:
Book::Book() : pointRev(&vectReviews) {};
string getAuthor() const;
string getCategory() const;
string getFormat() const;
string getIsbn() const;
string getPubDate() const;
string getSynopsis() const;
string getTitle() const;
vector<Review>* getReviews();
void setAuthor(string author);
void setCategory(string category);
void setFormat(string format);
void setIsbn(string isbn);
void setPubDate(string pubDate);
void setSynopsis(string synopsis);
void setTitle(string title);
friend ostream& operator <<(ostream& out, Book& book);
vector<Review> *getPointRev() const;
vector<Review> getVectReviews() const;
void setPointRev(vector<Review> *pointRev);
void setVectReviews(vector<Review> vectReviews);
};
#endif /* BOOK_H_ */
book. cpp
#include "book.h"
string Book::getAuthor() const
{
return author;
}
string Book::getCategory() const
{
return category;
}
string Book::getFormat() const
{
return format;
}
string Book::getIsbn() const
{
return isbn;
}
string Book::getPubDate() const
{
return pubDate;
}
string Book::getSynopsis() const
{
return synopsis;
}
string Book::getTitle() const
{
return title;
}
void Book::setAuthor(string author)
{
this->author = author;
}
void Book::setCategory(string category)
{
this->category = category;
}
void Book::setFormat(string format)
{
this->format = format;
}
void Book::setIsbn(string isbn)
{
this->isbn = isbn;
}
void Book::setPubDate(string pubDate)
{
this->pubDate = pubDate;
}
void Book::setSynopsis(string synopsis)
{
this->synopsis = synopsis;
}
void Book::setTitle(string title)
{
this->title = title;
}
vector<Review> *Book::getPointRev() const
{
return pointRev;
}
vector<Review> Book::getVectReviews() const
{
return vectReviews;
}
void Book::setPointRev(vector<Review> *pointRev)
{
this->pointRev = pointRev;
}
void Book::setVectReviews(vector<Review> vectReviews)
{
this->vectReviews = vectReviews;
}
vector<Review>* Book::getReviews()
{
return pointRev;
}
ostream& operator <<(ostream& out, Book& book)
{
out<<"\nTitle: "<<book.getTitle()<<endl;
out<<"Author: "<<book.getAuthor()<<endl;
out<<"Publish Date: "<<book.getPubDate()<<endl;
out<<"ISBN: "<<book.getIsbn()<<endl;
out<<"Category: "<<book.getCategory()<<endl;
out<<"Format: "<<book.getFormat()<<endl;
out<<"Synopsis: "<<book.getSynopsis()<<endl;
cout<<"\n--- Reviews ---"<<endl;
// vector<Review>* revs = book.getReviews();
for(unsigned int h = 0; h < book.getReviews()->size(); h++)
{
cout<<"Review by: "<<book.getReviews()->at(h).getUsrFName()<<" "<<book.getReviews()->at(h).getUsrLName()<<endl;
cout<<"Rating: "<<book.getReviews()->at(h).getRating()<<endl;
cout<<"Review: "<<book.getReviews()->at(h).getReview()<<endl;
}
return out;
}
review.h
#ifndef REVIEW_H_
#define REVIEW_H_
#include <string>
using namespace std;
class Review
{
private:
int rating;
string review;
string usrFName;
string usrLName;
public:
int getRating() const;
string getReview() const;
void setRating(int rating);
void setReview(string review);
string getUsrFName() const;
string getUsrLName() const;
void setUsrFName(string usrFName);
void setUsrLName(string usrLName);
};
#endif /* REVIEW_H_ */
review.cpp
#include "review.h"
int Review::getRating() const
{
return rating;
}
string Review::getReview() const
{
return review;
}
void Review::setRating(int rating)
{
this->rating = rating;
}
string Review::getUsrFName() const
{
return usrFName;
}
string Review::getUsrLName() const
{
return usrLName;
}
void Review::setUsrFName(string usrFName)
{
this->usrFName = usrFName;
}
void Review::setUsrLName(string usrLName)
{
this->usrLName = usrLName;
}
void Review::setReview(string review)
{
this->review = review;
}
From the behavior you describe, a copy-constructor is running and making two objects that point to the same vector. push_back does indeed use a copy-constructor.
But your first code snippet doesn't make a bunch of copies of the same Book, but a new Book is created on each loop iteration (and then copied into vectBooks.
If Book doesn't have a correct user-defined copy-constructor then you aren't managing pointRev correctly. From the observed behavior, I believe that you have a destructor which frees pointRev, and then the copy inside vectBooks is left with a dangling pointer. Everything after this falls into the category of undefined behavior according to the standard, meaning "anything can happen" Then the next Book happens to reuse the same memory area, so all the instances of Book end up with the same value in the wild pointer. And then updating any one changes the vector (which isn't even alive anymore) seen by all Book instances.
Why are you using a pointer to a std::vector anyway? Much better to just put the vector into the class as a direct data member, which lets the compiler construct, copy, and destruct it automatically with no additional help from you.
Of course, it's entirely possible that you've made vectReviews a global variable, and then every single book points to the same instance. That would a review show up in all books simultaneously, because they share the vector you're adding it to.
Related
I'm trying to read in a list of Animals, this works fine. Then I want to split each string into two substrings for the name and cmc, this also works fine. But my cout doesn't work.
For example, my animal.txt is:
Dog|hi
cat|miau
cow|hihi
My output of the for loop should look like this:
Dog
cat
cow
But the actual output is:
cow
cow
cow
Here is my Animal.cpp:
#include <string>;
#include <vector>;
#include <fstream>;
#include "Animal.h"
using namespace std;
string cmc;
string name;
void Animal();
void Animal(string nameA) {
name = nameA;
}
void Animal(string nameA, string cmcValue) {
name = nameCard;
cmc = cmcValue;
}
void Animal::setName(string names)
{
name = names;
}
void Animal::setCmc(string cmcvalue) {
cmc = cmcvalue;
}
std::string Animal::getName() {
return name;
}
std::string Animal::getCmc() {
return cmc;
}
void Animal::openfileAnimal() {
ifstream inFileAnimal;
inFileAnimal.open("Animals.txt");
if (inFileAnimal.fail()) {
cerr << "error open this file" << endl;
exit(1);
}
string itemsAnimal;
std::vector<Animal> AllAnimals;
while (getline(inFileAnimal, itemsAnimal)) {
Animal c;
string t1 = itemAnimal;
size_t pos = t1.find("|");
//name (setname(sub))
string sub = t1.substr(0, pos);
c.setName(sub);
string t2 = t1.substr(sub1.length() + 1, t1.length());
string sub2 = t2.substr(0, t2.length());
c.setCmc(sub2);
AllAnimals.push_back(c);
}
for (int i = 0; i < 2; i++) {
std::cout <<AllAnimals.at(i).getName() << endl;
}
}
I read nother StackOverflow questions like mine, but for my example all the solutions don't work. So where is my problem? I guess it's something like I am modifying the same memory over and over.
You have global variables instead of class data members:
string cmc;
string name;
void Animal();
void Animal(string nameA) {
name = nameA;
}
void Animal(string nameA, string cmcValue) {
name = nameCard;
cmc = cmcValue;
}
As a result, you keep only the latest assigned values. Moreover, something that you probably treat as a constructor, is not a constructor at all. The constructor should look like:
Animal::Animal(string nameA, string cmcValue)
: name(nameA), cmc(cmcValue) {
}
Note that initialization list syntax: that allows to avoid the mistakes like your's.
By the way, in your code nameCard is not defined at all.
class Client
{
public:
Client(int id, string title, int age):
~Client();
void addTW(int id, string title, int age);
int getID() const {return id;}
string getTitle() const {return title;}
int getAge() const {return age;}
private:
int id;
string title;
int age;
};
I have two functions:
load(), which is loading input .txt file - file's having titles of movies and age you need to have in order to watch the movie (e.x. Pulp Fiction - 16) and
addTW(int id, string title, int age), which adds Movies.
So, while adding a movie, you need to type id, title and age. I want to make that you cannot add movie if you're under a certain age (e.x. 16 or whatever). Age must be re-added from the .txt file. Basically age in connected with and only title.
I've never used .txt files. So I have no idea how to start.
#include <fstream>
void Client::addTW(int id, string title, int age)
{
int i, n = tw.size();
for(i = 0;i<n;i++)
{
ToWatch* newTW = new ToWatch(id, title, age);
tw.push_back(newTW);
return;
}
}
void Client::load()
{
ifstream input;
input.open("input.txt");
if(input.fail())
{ cout<<"Failure"<<endl;}
else
{
string s;
while(input>>s)
{
cout<<s<<" ";
}
}
input.close();
}
I am not sure, if the design of your class is OK. This you can find out by yourself.
I can help you with reading the file and extracting the age for a given title:
Please see:
#include <iostream>
#include <fstream>
#include <string>
unsigned int getAgeFromFile(const std::string& title) {
// We set a default age of 0. So, if we cannot find the title in the list, then everybody can look it
unsigned int resultingAge{ 0 };
// Define an ifstream variable. Use its constructor, to open the file, then check, if open was ok
if (std::ifstream fileMovies("input.txt"); fileMovies) {
// Read all lines in the text file in a loop with std::getline. std::getline will return false,
// if we are at end-of-file or in case of some other error. Then the loop will stop
for (std::string line{}; std::getline(fileMovies, line); ) {
// So, now we have a line from the file in tour "line" variable.
// Check, if the searched title is in it
if (line.find(title) != std::string::npos) {
// Ok, we found the title in this line. Now, we need to extract the age.
// It is at the end of the line and separated by a space. So, search from the end of the line for a space
if (size_t pos{ line.rfind(' ') }; pos != std::string::npos) {
// We found a space. Now, convert the number.
resultingAge = std::stoul(line.substr(pos));
}
}
}
}
// return result or default value, if not found
return resultingAge;
}
In your addTW function you need to insert one line before the
push_back.
if (age > getAgeFromFile(title))
Hope this helps.
Compiled and tested with VS2019 and C++17
In binary file, there are some information which is student id, name and GPA for three students. I want to read them. id is integer type and GPA is float type. name is string type and the size is not fixed because the size of name is different each other. I want to get id, name and GPA for each student at function of student class.
probably, I think I have a problem at loading name.
I want to know how to fix it.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class Student {
private:
int id;
string name;
float GPA;
string line;
public:
Student() {};
Student(string line_) :line(line_) { };
int get_id();
string get_name();
float get_GPA();
void save_bi(ofstream &of);
void load_bi(ifstream &inf);
void save_txt(ofstream &of);
void print();
};
void Student::load_bi(ifstream &inf)
{
inf.read((char*)&id, sizeof(id));
inf.read((char*)&name, sizeof(name));
inf.read((char*)&GPA, sizeof(GPA));
//a.push_back(name);
}
int main()
{
ifstream inf;
inf.open("student_data.bin", ios::in | ios::binary);
if (!inf.is_open())
cout << "file open failure." << endl;
Student A;
A.load_bi(inf);
A.print();
Student B;
B.load_bi(inf);
B.print();
Student C;
C.load_bi(inf);
C.print();
inf.close();
return 0;
}
it is error message.
inline void _Container_base12::_Orphan_all() noexcept
{ // orphan all iterators
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myproxy != nullptr)
{ // proxy allocated, drain it
_Lockit _Lock(_LOCK_DEBUG);
for (_Iterator_base12 **_Pnext = &_Myproxy->_Myfirstiter;
*_Pnext != nullptr; *_Pnext = (*_Pnext)->_Mynextiter)
(*_Pnext)->_Myproxy = nullptr;
_Myproxy->_Myfirstiter = nullptr;
}
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
Exception thrown: read access violation.
_Pnext was 0x13AED64.
A simple example of serializing your structure:
class Student
{
int id;
string name;
float GPA;
string line;
public:
void binary_write(std::ostream& out) const;
void binary_read(std::istream& input);
};
void Student::binary_write(std::ostream& out) const
{
out.write(&id, sizeof(id));
std::string::size_type length(name.length());
out.write(&length, sizeof(length));
out.write(name.data(), length);
out.write(&GPA, sizeof(GPA));
length = line.length();
out.write(line.data(), length);
}
void Student::binary_read(std::istream& input)
{
input.read(&id, sizeof(id));
std::string::size_type length;
input.read(&length, sizeof(length));
char * temp = new char[length];
input.read(temp, length);
name = std::string(temp);
input.read(&GPA, sizeof(GPA));
delete[] temp;
input.read(&length, sizeof(length));
temp = new char[length];
input.read(temp, length);
line = std::string(temp);
delete [] temp;
}
The above code uses the technique of writing the length of the string, followed by the string contents. The reading consists of using a temporary buffer, then reads the text into the buffer and creates a std::string from the buffer.
There are other methods to read in a string (search your C++ reference for "c++ string input iterator), but this illustrates the point.
The problem with writing pointers, and std::string contains pointers, is that there is no guarantee that the operating system will load your program into the exact same place in memory at every invocation.
The binary_write function is declared as const since it doesn't change any of the Student data members. This technique is called "const correctness". Search the internet for its definition.
The default accessibility for class is private, so I removed the private from the section declaring the variables.
I created a post here last week but it was for a struct.
Looking to add comma delimited values in a text file to members of a struct
I have a text file here with two values, a name and a score. I have a student class that has 4 members which are seen below.
I am looking to add the values in the text file to the corresponding members in the class separating by comma.
First five rows of the students.txt file;
Nubia,Dufrene,70
Louisa,Trippe,49
Aline,Deniz,34
Shery,Munk,63
Angila,Ping,89
My current code;
studentType.h
class q1studentType
{
//Private Memebrs
std::string studentFName;
std::string studentLName;
std::string testScore;
char grade;
public:
//Constructor
q1studentType(std::string studentFName,std::string studentLName, std::string testScore);
//Get & Set
std::string const getStudentFName() { return studentFName; }
std::string const getStudentLName() { return studentLName; }
std::string getTestScore() const { return testScore; }
void setStudentFName(std::string fN) { q1studentType::studentFName = fN; }
void setStudentLName(std::string lN) { q1studentType::studentLName = lN; }
void setTestScore(std::string ts) { q1studentType::testScore = ts; }
//Member Functions
void printStudent();
int stringToInt(std::string convertMe);
//std::vector<q1studentType> initStudents();
~q1studentType();
};
studentType.cpp
q1studentType::q1studentType(std::string sFN, std::string sLN, std::string ts)
{
studentFName = sFN;
studentLName = sLN;
testScore = ts;
stringToInt(testScore);
//assignGrade();
}
main.cpp
void initstudents() {
std::vector<q1studentType> students;
std::ifstream inFile("students.txt");
for (q1studentType i;
getline(inFile, i.setStudentFName, ',')
&& getline(inFile, i.setStudentLName, ',')
&& getline(inFile, i.setTestScore)
; )
{
studentVector.push_back(i);
}
}
I think the function in the main does not work because getline doesnt accept the setMethod as a param.
I cant use the class members in the for loop eg. i.studentFName because they are private and only accessible inside the class.
What is the best approach? Should I have the initstudents in the student.cpp file or the main?
I have a list of video game characters that are organized in a certain way.
I want to be able to take their names only from the list and sort it alphabetically.
The list is formatted by:
Last Name, First Name, Game, Relase Date, Score, Developer
The list is:
Snake, Solid, Metal Gear Solid, 9/3/1998, 94, Konami
Drake, Nathan, Uncharted, 11/19/2007, 90, Naughty Dog
Guy, Doom, Doom, 5/13/1993, 95, iD
The output I want is:
Drake, Nathan
Guy, Doom
Snake, Solid
I can print out there names only in the order in which they are in the text file. How do I compare the last names, then print out the full names?
Here is my code so far:
#include <fstream>
#include <iostream>
#include <string>
#include <time.h>
#include <cstdlib>
#include <sstream>
using namespace std;
ifstream inFile;
class Characher{
private:
string first;
string last;
public:
Character(){};
void getLast(){
if(inFile.is_open()){
getline(inFile, last,',');
} else {
cout<<"Hmm.."<<endl;
}
}
void getFirst(){
if(inFile.is_open()){
getline(inFile, first,',');
} else {
cout<<"No First Name here..."<<endl;
}
}
void printLast(){
cout<<last<<",";
}
void printFirst(){
cout<<first<<endl;
}
};
class Dev{
private:
string Developer;
public:
Dev(){};//null constructor
void printDeveloper(){
cout<<"Developer: "<<Developer<<endl;
}
void getDeveloper(){
if(inFile.is_open()){
getline(inFile, Developer);
} else {
cout<<"Nothing here..."<<endl;
}
}
};
class Game{
private:
string game;
public:
Game(){};
void getGameName(){
if(inFile.is_open()){
getline(inFile, game,',');
} else{
cout<<"What game was they frum?"<<endl;
}
}
void printGame(){
cout<<"Game: "<<game;
}
};
class RelDate_And_Score{
private:
string ReleaseDate;
string Score;
public:
RelDate_And_Score(){};
void GetRelDate(){
if(inFile.is_open()){
getline(inFile, ReleaseDate, ',');
} else{
cout<<"Could not find Release Date"<<endl;}
}
void getScore(){
if(inFile.is_open()){
getline(inFile, Score, ',');
} else{
cout<<"Could not find Score"<<endl;}
}
void PrintDate(){
cout<<"Release Date: "<<ReleaseDate<<" | ";}
void PrintScore(){
cout<<"Score: "<<Score<<endl;}
};
int main(){
inFile.open("Games.dat");
Dev d;
Characher c;
RelDate_And_Score r;
Game g;
for (int i=0; i<3; i++)
{
c.getLast();
c.getFirst();
g.getGameName();
r.GetRelDate();
r.getScore();
d.getDeveloper();
c.printLast();
c.printFirst();
}
return 0;
}
I see you work directly with the file for each method (getLast() etc). This is not the best way to do it, as accessing the file is costly and less efficient.
You should construct a representation of the file in memory: Start with representing each row as a Character class such as:
class Character
{
public:
Character(const string & firstname, const string & secondname, const string & game, const string & releasedate, const string & score, const string & developer)
:
m_firstname(firstname), m_secondname(secondname), m_game(game), m_releasedate(releasedate), m_score(score), m_developer(developer)
{}
private:
string m_firstname, m_secondname, m_game, m_releasedate, m_score, m_developer;
};
Open the file, read each row, construct a Character using the parsed strings (split by commas).
As tadman proposed in a comment, you can use the std::sort method to order the Character by names. Implement the operator< in the Character class such as:
class Character
{
//...
bool operator<(const Character & c)
{
return m_firstname < c.m_firstname;
}
//...
};
So you can use the std::sort on your vector<Character> m_characters
std::sort(m_characters.begin(), m_characters.end());