Question

I have a simple class City, which contains id, name, sizeOfPopulation and coordinates x and y. I have a text file, which looks like this:

City;1;Stockholm;300000;45;78;
City;2;Helsinky;451200;11;74;
City;3;Prague;897455;12;85;

Each line means following: The first in an indicator, that it is a city. Next is ID of city, name, size of population, x and y.

I want to read this file and creates a vector with cities. I know, that I need to overload an operator <<.

friend istream& operator >> (istream& is, City& c)
{
    // I do not know
}

After that, I would be able to loads cities from a file in for loop. Can you help me please with overloading operator?

Was it helpful?

Solution

Like this:

#include <istream>
#include <sstream>

friend istream& operator >> (istream& is, City& c)
{
    // get whole line
    string line;
    if (!getline(is, line))
    {
        // error reporting and exit goes here...
    }

    // replace all ';' to ' '
    for (int i = 0; i < line.length(); i++)
    {
        if (line[i] == ';')
            line[i] = ' ';
    }

    // start parsing here
    istringstream iss(line);
    string tmp;
    if (iss >> tmp >> c.id >> c.name >> sizeOfPopulation >> c.x >> c.y)
    {
        // error reporting and exit goes here...
    }

    return input;
}

To read City objects in a loop, you can use:

City c;
while (is >> c) // suppose "is" is the opened input stream
{
    // successfully read a City, stored in c
}

OTHER TIPS

In this case, I'd use getline to read the line, then break it up on the ';', using something like:

std::vector<std::string>
split( std::string const& source, char separ )
{
    std::vector<std::string> results;
    std::string::const_iterator start = source.begin();
    std::string::const_iterator end = source.end();
    std::string::const_iterator next = std::find( start, end, separ );
    while ( next != end ) {
        results.push_back( std::string( start, next ) );
        start = next + 1;
        next = std::find( start, end, separ );
    }
    results.push_back( std::string( start, end ) );
}

(You may want to extend this to trim leading and trailing spaces, etc.)

For the fields which are strings, you can just index into the std::vector; for the others, use the string value to initialize an std::istringstream to convert them (or std::stoi, if you have C++11). So you'd end up with something like:

std::istream&
operator>>( std::istream& source, City& dest )
{
    std::string record;
    if ( std::getline( source, record ) ) {
        std::vector<std::string> fields = split( record );
        if ( fields.size() != 7 || fields[0] != "City" || fields[6] != "" ) {
            source.setstate( std::ios_base::failbit );
        } else {
            try {
                dest = City( std::stoi( vector[1] ),
                             std::vector[2],
                             std::stoi( vector[3] ),
                             Coordinates( std::stoi( vector[4] ),
                                          std::stoi( vector[5]) ) ) );
            catch ( ... ) {
                source.setstate( std::ios_base::failbit );
            }
        }
    }
    return source;
}

(The need for the try ... catch isn't very nice, but that's the way std::stoi reports its errors.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top