Hi I'm trying to fill in an array of a class object I created. Input is from a text file. The text file has strings and numbers. I got the first set of info in but the rest of the file won't read in, any thoughts would be appreciated!
class mess
{
private:
string name;
float age, weight, height;
public:
void setname(string a) {name=a;}
void setage(float b){age=b;}
//etc.
string getname(){return name;}
float getage(){return age;}
//etc.
}
ifstream input;
input.open("test.txt");
mess people[2]
string str;
float num;
for(inti=0; i<2; i++)
{
getline(input,str,'\n');
people[i].setname(str);
input >> num;
people[i].setage(num);
input >> num;
people[i].setweight(num);
input >> num;
people[i].setheight(num);
}
for(inti=0; i<2; i++)
{
cout << people[i].getname() << endl;
cout << people[i].getage() << endl;
cout << people[i].getweight() << endl;
cout << people[i].getheight() << endl;
}
test.txt
jack
17 150.3 5.10
Amy
18 110.4 5.11
Output:
Jack
17
150.3
5.10
(blank)
0
0
0
The problem here is that when you use the input operator >> it will leave the newline after the last number for the first record. This means that the next getline call will read that newline as an empty line, and then the numbers will fail to read.
There are a couple of ways to solve this. The first is to discard all text in the input until newline after reading the last number in the record. For this you can do e.g.
// All other input in loop
input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
Read about the ignore function.
Another way is to read the second line, complete, and put it into an std::istringstream and then read out the numbers from it:
// Reading the name...
std::string numbers;
std::getline(input, numbers);
std::istringstream istr(numbers);
istr >> num;
people[i].setage(num);
// ...
Also note that the third argument to std::getline already defaults to a newline, so if you're using it to read lines, then you don't need to provide it.
I suggest you overload operators << and >> in your class:
class mess
{
private:
string name;
float age, weight, height;
public:
void setname(string a) {name=a;}
void setage(float b){age=b;}
//etc.
string getname(){return name;}
float getage(){return age;}
//etc.
friend std::istream& operator>>(std::istream& inp, mess& m);
friend std::ostream& operator<<(std::ostream& out, const mess& m);
}
std::istream& operator>>(std::istream& inp, mess& m)
{
std::getline(inp, m.name);
inp >> m.age;
inp >> m.weight;
inp >> m.height;
return inp;
}
std::ostream& operator<<(std::ostream& out, const mess& m)
{
out << m.name << endl;
out << m.age << endl;
out << m.weight << endl;
out << m.height << endl;
return out;
}
This simplifies your input to:
std::vector<mess> people;
mess p;
while (input_file >> p)
{
people.push_back(p);
}
Your output looks like:
for (unsigned int i = 0; i < people.size(); ++i)
{
cout << people[i];
cout << "\n";
}
I would define the operator<< to write out an object of your class. Then defined the operator>> to read an object of your class. Then you can use std::istream_iterator to read the values directly into the container:
class M
{
private:
string name;
float age;
float weight;
float height;
public:
<STUFF>
friend std::ostream& operator<<(std::ostream& s, M const& data)
{
return s << data.age << " "
<< data.weight << " "
<< data.height << " "
// Put name on the edn because it may contain space.
// So we want to read it off using std::getline
<< data.name << "\n";
}
friend std::istream& operator>>(std::istream& s, M& data)
{
s >> data.age >> data.wight >> data.height;
std::getline(s, data.name);
// Strip leading space (see output operator)
data.name = data.name.substring(1);
return s;
}
};
Then easy to use in most containers.
int main()
{
std::ifstream f("data");
// OK.
// A vector is not actually an array.
// But you can use the same principle with a manual loop.
// as std::istream_iterator is just a normal iterator.
std::vector<M> data(std::istream_iterator<M>(f), std::istream_iterator<M>());
}
Related
This question already has answers here:
std::cin input with spaces?
(8 answers)
Why does std::getline() skip input after a formatted extraction?
(5 answers)
Closed 8 months ago.
I'm doing a crud in C++, but in the data entry, I can't add space in the name, it bugs, I tried to use ignore and getline, it worked but the first letter disappears when reading the variable, using only getline, it doesn't work
class banca
{
private:
char name[100];
char email[100];
char course_aluno[100];
int semestre;
int id;
public:
friend istream& operator>>(istream& in, banca& p);
friend ostream& operator<<(ostream& out, banca& p);
}
istream& operator>>(istream& in, banca& p)
{
cout << "--------ALUNO--------\n";
cout << "Name: \n" << endl;
cin.ignore();
cin.getline(p.name, 100);
cout << "Email: \n";
cin.ignore();
cin.getline(p.email, 100);
cout << "Course : \n";
cin.ignore();
cin.getline(p.course_aluno, 100);
cout << "Semestre: \n";
in >> p.semestre;
cout << "ID: \n";
in >> p.id;
}
You are basically running into an old problem, when working with input functions.
You need to learn about the difference between formatted and unformatted input.
The extraction and inserter operators >> and << are formatted input functions. They read or write a sequence of characters and format them. Example:
int i = 42;
std::cout << i;
This is an example of formatted IO. Although 42 has the binary representation of 101010 you will see the character sequence "42" on the screen.
Same is valid vice versa. If you want to read a number from the user, you typically use something like
int i;
std::cin >> i;
And, although you enter the characters '4' and '2' and ENTER ('\n') in the terminal, you will get the value 42 in your variable "i". The ENTER ('\n') is still in the input stream and has not yet been consumed. This is an additional problem that I will explain in a minute.
If you want to read a string, like "Peter Parker", and use
std::string name{};
std::cin >> name;
You will only get "Peter", because the formatted input functions (per default) stop converting after they hit a white space. So, after "Peter" has been read. The whitespace will not be consumed and will be still in the input stream.
For this reason you will use std::getline to read a string. This will not stop at a whitespace, but at a delimiter, per default at '\n'. So it will read a complete line.
From reading above, you may now see the problem. If there is a transition from formatted to unformatted input there is often the problem that there is still a whitespace (often the ENTER '\n', in your case the space) in the input stream. And you need to eliminate this whitespace.
You tried it with std::ignore, but there is a better possibility: std::ws. You may read here about it. This will often be the better approach. You may use it after the transition from formatted to unformatted input the eat away the non wanted white spaces. You can also use it in general if you want to eat up leading white spaces using unformatted input. (Beware: the formatted input functions like >> will skip the white spaces (per default)).
ignore is a unformatted input function and will actively wait for characters. It is an input function. It will not return until it read something. So, be careful. It will often be just used in error cases.
With the above knowledge, we could rewrite your code like the below:
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
class banca
{
private:
std::string name;
std::string email;
std::string course_aluno;
int semestre;
int id;
public:
friend std::istream& operator >>(std::istream& in, banca& p);
friend std::ostream& operator <<(std::ostream& out, const banca& p);
};
std::istream& operator>>(std::istream& in, banca& p)
{
std::cout << "\n\n--------ALUNO--------\n";
std::cout << "Name:\n";
std::getline(in >> std::ws, p.name);
std::cout << "Email:\n";
std::getline(in, p.email);
std::cout << "Course:\n";
std::getline(in, p.course_aluno);
std::cout << "Semestre:\n";
in >> p.semestre;
std::cout << "ID:\n";
in >> p.id;
return in;
}
std::ostream& operator <<(std::ostream& out, const banca& p) {
return out << "\nName:\t\t " << p.name << "\nEMail:\t\t " << p.email << "\nCourse:\t\t "
<< p.course_aluno << "\nSemestre:\t " << p.semestre << "\nID:\t\t " << p.id << '\n';
}
int main() {
banca b1,b2;
std::cin >> b1;
std::cout << b1;
std::cin >> b2;
std::cout << b2;
}
But beware. Input functions may fail. Check the return code of each input function. And if you have an error, then use ignore.
Example. If you expect a number and enter a string, then the formatted input function will fail, and all invalid characters will still be in the stream. Enter "abc" for the semester and you will see it.
Solution. Check result of IO function:
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <limits>
class banca
{
private:
std::string name{};
std::string email{};
std::string course_aluno{};
int semestre{};
int id{};
public:
friend std::istream& operator >>(std::istream& in, banca& p);
friend std::ostream& operator <<(std::ostream& out, const banca& p);
};
std::istream& operator>>(std::istream& in, banca& p)
{
std::cout << "\n\n--------ALUNO--------\n";
std::cout << "Name:\n";
std::getline(in >> std::ws, p.name);
std::cout << "Email:\n";
std::getline(in, p.email);
std::cout << "Course:\n";
std::getline(in, p.course_aluno);
std::cout << "Semestre:\n";
in >> p.semestre;
std::cout << "ID:\n";
in >> p.id;
return in;
}
std::ostream& operator <<(std::ostream& out, const banca& p) {
return out << "\nName:\t\t " << p.name << "\nEMail:\t\t " << p.email << "\nCourse:\t\t "
<< p.course_aluno << "\nSemestre:\t " << p.semestre << "\nID:\t\t " << p.id << '\n';
}
int main() {
banca b1,b2;
if (std::cin >> b1)
std::cout << b1;
else {
std::cerr << "\nError: Problem with input\n\n";
std::cin.clear(); // unset failbit
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // skip bad input
}
if (std::cin >> b2)
std::cout << b2;
else {
std::cerr << "\nError: Problem with input\n\n";
std::cin.clear(); // unset failbit
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // skip bad input
}
}
I am trying to read a file of the following format
id1 1 2 3
id2 2 4 6
id3 5 6 7
...
using this code
Dataset::Dataset(ifstream &file) {
string token;
int i = 0;
while (!file.eof() && (file >> token)){
// read line tokens one-by-one
string ID = token;
vector<int> coords;
while ((file.peek()!='\n') && (!file.eof()) && (file >> token)) {
coords.push_back(atoi(token.c_str()));
}
points.push_back(new Point(ID, coords));
i++;
}
cout << "Loaded " << i << " points." << endl;
}
But it tells me I have read 0 points. What am I doing wrong?
Edit: I am openning this using input_stream.open(input_file) and file.good() returns true.
Edit #2: actually .good() returns true the first time and then false. What is that all about?
Edit #3: GUYS. IT'S FREAKING WINDOWS. When i put the path as Dataset/test.txt by cin it works and when I do it like Dataset\test.txt by the commandline it doesn't...
Now the problem is it seems not stop at new lines!
Edit #4: Freaking windows again! It was peeking '\r' instead of '\n'.
Here's an idea: overload operator>>:
struct Point
{
int x, y, z;
friend std::istream& operator>>(std::istream& input, Point& p);
};
std::istream& operator>>(std::istream& input, Point& p)
{
input >> p.x;
input >> p.y;
input >> p.z;
input.ignore(10000, '\n'); // eat chars until end of line.
return input;
}
struct Point_With_ID
: public Point
{
std::string id;
friend std::istream& operator>>(std::istream& input, Point_With_ID& p);
};
std::istream& operator>>(std::istream& input, Point_With_ID& p)
{
input >> p.id;
input >> static_cast<Point&>(p); // Read in the parent items.
return input;
}
Your input could look like this:
std::vector<Point_With_ID> database;
Point_With_ID p;
while (file >> p)
{
database.push_back(p);
}
I separated the Point class so that it can be used in other programs or assignments.
I managed to make it work by accounting for both '\r' and '\n' endings and ignoring trailing whitespace like this:
Dataset::Dataset(ifstream &file) {
string token;
int i = 0;
while (file >> token){
// read line tokens one-by-one
string ID = token;
vector<int> coords;
while ((file.peek()!='\n' && file.peek()!='\r') && (file >> token)) { // '\r' for windows, '\n' for unix
coords.push_back(atoi(token.c_str()));
if (file.peek() == '\t' || file.peek() == ' ') { // ignore these
file.ignore(1);
}
}
Point p(ID, coords);
points.emplace_back(p);
i++;
// ignore anything until '\n'
file.ignore(32, '\n');
}
cout << "Loaded " << i << " points." << endl;
}
Probably not the best of the solutions suggested but it's working!
You should not use eof() in a loop condition. See Why is iostream::eof inside a loop condition considered wrong? for details. You can instead use the following program to read into the vector of Point*.
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
class Point
{
public:
std::string ID = 0;
std::vector<int> coords;
Point(std::string id, std::vector<int> coord): ID(id), coords(coord)
{
}
};
int main()
{
std::vector<Point*> points;
std::ifstream file("input.txt");
std::string line;
int var = 0;
while (std::getline(file, line, '\n'))//read line by line
{
int j = 0;
std::istringstream ss(line);
std::string ID;
ss >> ID;
std::vector<int> coords(3);//create vector of size 3 since we already know only 3 elements needed
while (ss >> var) {
coords.at(j) = var;
++j;
}
points.push_back(new Point(ID, coords));
}
std::cout<<points.size()<<std::endl;
//...also don't forget to free the memory using `delete` or use smart pointer instead
return 0;
}
The output of the above program can be seen here.
Note that if you're using new then you must use delete to free the memory that you've allocated. This was not done in the above program that i have given since i only wanted to show how you can read the data in your desired manner.
You've baked everything up in a complex deserializing constructor. This makes the code hard to understand and maintain.
You have a coordinate, so make class for that, we can call it Coord, that is capable of doing its own deserializing.
You have a Point, which consists of an ID and a coordinate, so make a class for that, that is capable of doing its own deserializing.
The Dataset will then just use the deserializing functions of the Point.
Don't limit deserializing to ifstreams. Make it work with any istream.
Deserializing is often done by overloading operator>> and operator<< for the types involved. Here's one way of splitting the problem up in smaller parts that are easier to understand:
struct Coord {
std::vector<int> data;
// read one Coord
friend std::istream& operator>>(std::istream& is, Coord& c) {
if(std::string line; std::getline(is, line)) { // read until end of line
c.data.clear();
std::istringstream iss(line); // put it in an istringstream
// ... and extract the values:
for(int tmp; iss >> tmp;) c.data.push_back(tmp);
}
return is;
}
// write one Coord
friend std::ostream& operator<<(std::ostream& os, const Coord& c) {
if(not c.data.empty()) {
auto it = c.data.begin();
os << *it;
for(++it; it != c.data.end(); ++it) os << ' ' << *it;
}
return os;
}
};
struct Point {
std::string ID;
Coord coord;
// read one Point
friend std::istream& operator>>(std::istream& is, Point& p) {
return is >> p.ID >> p.coord;
}
// write one Point
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << p.ID << ' ' << p.coord;
}
};
struct Dataset {
std::vector<Point> points;
// read one Dataset
friend std::istream& operator>>(std::istream& is, Dataset& ds) {
ds.points.clear();
for(Point tmp; is >> tmp;) ds.points.push_back(std::move(tmp));
if(!ds.points.empty()) is.clear();
return is;
}
// write one Dataset
friend std::ostream& operator<<(std::ostream& os, const Dataset& ds) {
for(auto& p : ds.points) os << p << '\n';
return os;
}
};
If you really want a deserializing constructor in Dataset you just need to add these:
Dataset() = default;
Dataset(std::istream& is) {
if(!(is >> *this))
throw std::runtime_error("Failed reading Dataset");
}
You can then open your file and use operator>> to fill the Dataset and operator<< to print the Dataset on screen - or to another file if you wish.
int main() {
if(std::ifstream file("datafile.dat"); file) {
if(Dataset ds; file >> ds) { // populate the Dataset
std::cout << ds; // print the result to screen
}
}
}
Demo
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 2 years ago.
Improve this question
The code is supposed to open an existing text file, transfer the contents into the array, then create a new text file and then write the array contents into the new text file. The problem I'm having is that the code only outputs the last line of the content from the new text file.
file.open("Patient.txt", ios::in);
while (!file.eof()) {
file >> arr[i].name >> arr[i].DOB >> arr[i].address >> arr[i].Dr_name >> arr[i].V_date;
/*cout << arr[i].name << arr[i].DOB << arr[i].address << arr[i].Dr_name << arr[i].V_date << endl;*/
}
file.close();
files.open("Patients_2.txt");
if (files.is_open()) {
for (i; i < 30; i++) {
files << arr[i].name << arr[i].DOB << arr[i].address << arr[i].Dr_name << arr[i].V_date;
}
}
files.close();
patientss.open("Patients_2.txt");
cout << "Patient 2: " << endl;
while (!patientss.eof()) {
getline(patientss, line);
cout << line << endl;
}
patientss.close();
system("pause");
return 0;
}
IMHO, you should overload the formatted insertion and extraction operators in your patient class:
struct Patient
{
std::string name;
std::string dob;
std::string address;
std::string dr_name;
std::string v_date;
friend std::istream& operator>>(std::istream& input, Patient& p);
friend std::ostream& operator<<(std::ostream& output, const Patient& p);
};
std::istream& operator>>(std::istream& input, Patient& p)
{
std::getline(input, p.name);
std::getline(input, p.dob);
std::getline(input, p.address);
std::getline(input, p.dr_name);
std::getline(input, p.v_date);
return input;
}
std::ostream& operator<<(std::ostream& output, const Patient& p)
{
output << p.name << "\n";
output << p.dob << "\n";
output << p.address << "\n";
output << p.dr_name << "\n";
output << p.v_date << "\n";
return output;
}
The above makes input and output easier:
std::vector<Patient> database;
Patient p;
while (input_file >> p)
{
database.push_back(p);
}
const unsigned int quantity = database.size();
for (unsigned int i = 0; i < quantity; ++quantity)
{
output_file << database[i];
}
The above code also supports the concepts of encapsulation and data hiding. The Patient struct is in charge or reading its members because it knows the data types of the members. The code external to the Patient is only concerned with the input and output of a Patient instance (doesn't care about the internals).
This loop has a few problems:
while (!file.eof()) {
file >> arr[i].name >> arr[i].DOB ....
You never increase i so the same arr[i] will be overwritten time and time again.
You use !file.eof() as a condition to stop reading. eof() does not get set until after you've tried to read beyond the end of the file, which means that if you had increased i as you should, the last arr would be empty / broken. Instead check if the extraction from the stream succeeded. Since the stream is returned when you do stream >> var and has an overload for explicit operator bool() const which returns !fail() you can do this check directly in your loop:
while(stream >> var) { extraction success }
Using formatted input (>>) for string fields that are likely to contain spaces is however not a good idea. Your name, nung khual, would be split so nung would go into name and khual would go into DOB. It's better to use a field separator that is very unlikely to be included in anyone's name. \n is usually good and works well with std::getline.
std::getline returns the stream that you gave as an argument which means that you can chain getlines similarly to stream >> var1 >> var2, except it's a little more verbose.
getline(getline(stream, var1), var2) will put the first line in var1 and the second line in var2.
To make input and output a little simpler you can add stream operators for your data type and make the input stream operator use getline for your fields.
Example:
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
struct data_t {
std::string name;
std::string DOB;
std::string address;
std::string Dr_name;
std::string V_date;
};
// input stream operator using chained getlines
std::istream& operator>>(std::istream& is, data_t& d) {
using std::getline;
return getline(getline(getline(getline(getline(is,
d.name), d.DOB), d.address), d.Dr_name), d.V_date);
}
// output stream operator
std::ostream& operator<<(std::ostream& os, const data_t& d) {
return os << d.name << '\n'
<< d.DOB << '\n'
<< d.address << '\n'
<< d.Dr_name << '\n'
<< d.V_date << '\n';
}
int main() {
std::vector<data_t> arr;
if(std::ifstream file("Patient.txt"); file) {
data_t tmp;
while(file >> tmp) { // remember, no eof() needed
arr.push_back(tmp);
}
}
if(std::ofstream file("Patients_2.txt"); file) {
for(const data_t& d : arr) {
file << d;
}
}
if(std::ifstream patientss("Patients_2.txt"); patientss) {
data_t tmp;
while(patientss >> tmp) {
std::cout << tmp;
}
}
}
Saving the vector to a file works fine. But I'm looking for a simple way to load the saved data back into the vector.
This is a follow up question to two I asked previously.
1) C++ Trouble Inputting Data into Private Vector (invalid use)
2) Outputting Vector of Type Class
What's a simple way to iterate through the file and push_back() each element?
This is the class:
class Account
{
private:
string firstName;
string lastName;
string accountPass;
int accountID;
float accountBalance;
public:
static Account createAccount( int, float, string, string, string ); //creates new account
int getAccountID() const { return accountID; }
string getPass() const { return accountPass; }
string getFirstName() const { return firstName; }
string getLastName() const { return lastName; }
float getBalance() const { return accountBalance; }
friend std::ostream& operator << (std::ostream&, const Account&);
friend class BankingSystem;
}; //end of class Account
Account Account::createAccount( int ID, float balance, string pass, string first, string last )
{
Account a;
a.accountID = ID;
a.accountPass = pass;
a.firstName = first;
a.lastName = last;
a.accountBalance = balance;
return a;
}
std::ostream & operator << (std::ostream & os, const Account & acc)
{
os << setw(6) << acc.getAccountID();
os << setw(4) << acc.getPass();
os << setw(9) << acc.getFirstName();
os << setw(9) << acc.getLastName();
os << setw(9) << setprecision(2) << fixed << acc.getBalance();
return os;
}
If Accounts are the only thing written in your file you can read them all into your vector (or any push_back-able container) with this 1-liner:
std::copy(std::istream_iterator<Account>(file), std::istream_iterator<Account>(), std::back_inserter(vec));
You'll also need an operator>> analogous to the operator<< you already have.
Found the answer in this Question Using vector of user defined class type objects
for me it was solved by using:
while(!inBankSysFile.eof())
{
Account a;
inBankSysFile >> a.accountID;
inBankSysFile >> a.accountPass;
inBankSysFile >> a.firstName;
inBankSysFile >> a.lastName;
inBankSysFile >> a.accountBalance;
accounts_.push_back(a);
}
If you don't have any dynamic memory, you can read and write it to binary pretty easily using ifstream::read and ofstream::write and vector::data. Here's an example:
#include <iostream>
#include <vector>
#include <fstream>
using namespace std;
class Time
{
public:
Time(): hh(0),mm(0),ss(0) {}
Time(int h,int m,int s):hh(h),mm(m),ss(s) {}
int hh,mm,ss;
};
int main()
{
Time time1(11,22,33);
Time time2(44,55,66);
vector<Time> timeList;
timeList.push_back(time1);
timeList.push_back(time2);
vector<Time> timeList2;
timeList2.resize(2,Time());
ofstream fout;
fout.open("test.txt");
if(fout.is_open())
{
// vector.data returns a pointer to the beginning of its stored data
// 1st param: the location to read data from
// 2nd param: the amount of bytes to write to the file
fout.write((const char*)timeList.data(),sizeof(Time)*timeList.size());
fout.close();
}
ifstream fin;
fin.open("test.txt");
if(fin.is_open())
{
// 1st param: the location to write data to
// 2nd param: the amount of bytes to read from the file
// NOTE: make sure you've sized the vector appropriately before writing to it.
fin.read((char*)timeList2.data(),sizeof(Time)*timeList2.size());
for(int i=0;i<timeList2.size();++i) {
cout << timeList2[i].hh << ":" << timeList2[i].mm << ":" << timeList2[i].ss << "\n";
}
fin.close();
}
return 0;
}
NOTE: Reading/writing objects that use dynamic memory (including objects containing classes that contain dynamic memory, such as std::string), will require additional processing logic to handle reading and writing that data.
NOTE: Beware of variances in structural alignment padding when using the sizes of any objects.
The while loop I have while reading in from a file doesn't break. I'm not sure what the problem is. If you need any more information just ask.
Code:
#include <string>
#include <map>
#include <fstream>
#include <iostream>
#include <iterator>
using namespace std;
class Customer {
public:
string name;
string address;
Customer() {}
};
class Purchase {
public:
string product_name;
double unit_price;
int count;
Purchase() {}
Purchase(string pn, double up, int c) :product_name(pn), unit_price(up), count(c) {}
};
// Function Object for comparison in map container
struct Cmp_name {
bool operator()(const Customer& first, const Customer& second)
{ return first.name < second.name; }
};
// ostream overloads
ostream& operator<<(ostream& out, const Customer& c)
{
out << c.name << '\n'
<< c.address << '\n';
return out;
}
ostream& operator<<(ostream& out, const Purchase& p)
{
out << p.product_name << '\n'
<< p.unit_price << '\n'
<< p.count << '\n';
return out;
}
istream& operator>>(istream& in, Customer& c)
{
getline(in, c.name);
getline(in, c.address);
return in;
}
istream& operator>>(istream& in, Purchase& p)
{
getline(in, p.product_name);
in >> p.unit_price >> p.count;
return in;
}
int main()
{
cout << "Enter file to read orders from: \n";
string file;
cin >> file;
ifstream is(file.c_str());
if (!is) cerr << "File doesn't exist.\n";
multimap<Customer, Purchase, Cmp_name> orders;
while (!is.eof()) {
Customer c;
Purchase p;
is >> c;
is >> p;
orders.insert(make_pair(c,p));
}
for (multimap<Customer, Purchase, Cmp_name>::iterator it = orders.begin(); it!=orders.end(); ++it)
cout << it->first << it->second << "\n\n";
}
As for your Customer/Purchase ostream inserters, declare the second argument const& instead of non-const &. For example:
ostream& operator<<(ostream& out, Customer const& c)
That's necessary because the key in a map is immutable even if you're using a non-const iterator (modifying the key would invalidate whatever tree-sorting or hashing the map implementation uses.
It's best to check every istream extraction operation for success, and break out of the loop the first time one doesn't succeed. Your "is.eof()" isn't going to read any extra (e.g. whitespace) characters, so it may claim "!eof()" at the semantic end of file.
Something like:
for(;;) {
Customer c;
Purchase p;
if (!getline(is, c.name)) break;
if (!getline(is, c.address) break;
if (!getline(is, p.product_name) break;
if (!(is >> p.unit_price >> p.count)) break;
orders.insert(make_pair(c,p));
}
Since those all return the original istream, it's the same as having a "if (!is) break;" after every attempted input.
You can also simplify things somewhat by defining extractors for Customer and Purchase, e.g.
istream& operator>>(istream &i,Customer &c)
A failure to read a Customer would let you break out (the istream will evaluate as false if an eof stops the read from succeeding).
Obviously you can make some of the failed-input points "ok to eof" and give a specific error in all the other cases.