c++ read txt file and copy into two classes istream::peek()

194 views Asked by At
istream& operator>>(istream& s, vector<Order>& ord) {

    string c{};
    bool got_customer = false;
    Customer temp_customer{};
    vector<Purchase>vec_purchase{};
    while (s >> c) {
        cout << "Erstes Wort der Zeile: "<<c << endl;

        if (!got_customer) { //c[0] == '#'
            c.erase(c.begin()); //löscht erstes Element vom string
            int k_nummer{};
            string k_vname{};
            string k_nname{};
            string k_plz{};
            string k_stadt{};
            k_nummer = stoi(c); //string to int
            s >> k_vname >> k_nname >> k_plz>>k_stadt;
            k_vname += " " + k_nname;
            temp_customer = Customer(k_nummer, k_vname, k_plz, k_stadt);
            got_customer = true;
        } else {
            string p_name{};
            double p_preis{};
            int p_menge{};
            p_name = c;
            s >> p_preis>>p_menge;
            cout << p_name << "  " << p_preis << "  " << p_menge << endl;
            vec_purchase.push_back(Purchase{p_name, p_preis, p_menge});
        }

        cout<<s.peek()<<endl;
        if (s.peek() == '#') {
            ord.push_back(Order{temp_customer, vec_purchase});
            temp_customer = Customer{};
            vec_purchase.clear();
            got_customer = false;
        }
    }
}

This is the txt file:

#725454 Schenker Rudolf DE-30159 Hannover
Shirt 135.95 1
Tie 89.59 1
#987654 Orbison Roy US-TN37075 Hendersonville
Mug 1.49 3
T-Shirt 14.90 1
#123456 Petty Tom US-FL32641 Gainesville
Flashlight 12.95 2
#246802 Hite Bob US-CA90291 Venice
CannedBeans 0.89 10
CannedTomatoes 1.79 6
#246802 Hite Bob US-CA90291 Venice
CanOpener 0.48 1
Spoon 1.49 4
Needle 0.05 100

The lines starting with '#' are Customer objects. The other lines are Purchase objects.

I'm trying to peek into the upcoming char but im getting the same char again and again. In this case i always get '13' as output of s.peek().

€dit: Thank you @Mark Ransom. Here is the working code if anybody has the same problem. :)

istream& operator>>(istream& s, vector<Order>& ord) {
    string c{};
    bool got_customer = false;
    Customer temp_customer{};
    vector<Purchase>vec_purchase{};

    while (s >> c) {
        if (!got_customer) { 
            c.erase(c.begin()); //deletes first char of string
            int k_nummer{};
            string k_vname{};
            string k_nname{};
            string k_plz{};
            string k_stadt{};
            k_nummer = stoi(c); //string to int
            s >> k_vname >> k_nname >> k_plz>>k_stadt>>ws;
            k_vname += " " + k_nname;
            temp_customer = Customer(k_nummer, k_vname, k_plz, k_stadt);
            got_customer = true;
        } else {
            string p_name{};
            double p_preis{};
            int p_menge{};
            p_name = c;
            s >> p_preis>>p_menge>>ws;
            vec_purchase.push_back(Purchase{p_name, p_preis, p_menge});
        }

        if (s.peek() == 35||s.peek() == -1 ) {  //35 (in ascii) -> # , -1 -> end of txt file
            ord.push_back(Order{temp_customer, vec_purchase});
            temp_customer = Customer{};
            vec_purchase.clear();
            got_customer = false;
        }
    }
}
2

There are 2 answers

0
Mark Ransom On

When you read from a stream, the input will stop on the first whitespace character. The character with a decimal code of 13 is the ASCII CR, which comes at the end of every line in Windows; it is considered whitespace. You need to skip the whitespace to find the next significant character. You do this with std::ws.

s >> k_vname >> k_nname >> k_plz >> k_stadt >> std::ws;

s >> p_preis >> p_menge >> std::ws;
3
Sam Varshavchik On
 s >> p_preis>>p_menge;

This is intended to read the last two fields on a line with a record. Immediately afterwards:

 if (s.peek() == '#') {

Your obvious intent here is to peek at the first character on the next line, to check if the next line starts with #, or not.

Well, this is not going to happen, like that...

The problem is that the >> operator does not consume the end of line character after the processed input. peek() here is going to get you the newline character, instead. Surprise.

Given a sample input line:

 Tie 89.59 1

The "1" part will be read by >>p_menge, and stored into menge. The next character after 1 will be the newline character, and that's what peek() will happily give you.

This is completely the wrong approach for this task. There's only one correct approach to read files containing lines of text:

std::getline()

That's it. std::getline(), and nothing else. Learn how to use it. I'll make your life much simpler.

You can certainly use the >> operator to read lines from your input file, piecemeal, one field at a time, but as you've discovered this is an error-prone and fragile process. Too many minefields and pitfalls.

Well, of course, it's possible to do this right using the >> operator, but it's not always obvious how to do it, and one needs to fully understand all the nuances of formatted input operations.

It's unfortunate that too many introductory C++ books begin right off the bat presenting examples that use the >> operator to read input consisting of lines of text. This only confuses most beginners, and leads to problems, and misunderstandings, like these.

You need to rewrite your program to use std::getline() to read one line of text at a time. End of story.

Once you've read the whole line of text into a std::string, and checked whether or not it starts off with the # character: if you feel like it then you can stuff the whole line into a std::istringstream, and then use the >> operator, in a controlled environment.