简体   繁体   中英

Difficulty reading from a file

I have a file with comma separated values

M,21,Hazel
F,49,Stephen

I am sending ifstream into a function that takes in istream to read the line.

ifstream file(fileName);
char gender;
file.get(gender);
file.ignore();  // ignore comma

if (gender == 'M') {
  Gender* tmp = new Male;
  file >> *tmp;
} else if (gender == 'F') {
  Gender* tmp = new Female;
  file >> *tmp;
}

The first character up to the comma is read correctly, but when I send it to read it asks for user input when it is not wanted. It doesnt read the rest of the file ie "49,Stephen"

istream& operator>>(istream& istr, ReadW& ref) {
  return ref.read(istr);
}

istream& read(istream& is) {
  char tName[16];
  is >> age;
  is.ignore();  // ignore comma
  is.getline(tName, 16, ',');
}

Basically you are asking about reading a csv file and splitting it into tokens. If you search for this here on SO, you will find many many posts which explain how to do.

But in your case it maybe simpler. If it is guaranteed that the source file has exactly the format abouve, without additional spaces before or after the comma, then you can use the standard extractor mechanisms for streams. You just will read the comma into a dummy variable. This may look like:

// What we want to read
char gender{};
unsigned int age{};
std::string name{};
// This is a dummy
char comma{};

while (testDataStream >> gender >> comma >> age >> comma >> name) {

This will read the first character int the variable gender and then read the comma and put it into the comma variable. This we will simply not use. Then, we will continue to extract more values in the same way.

If the structure of the source file is different, it will not work. For example, if the first variable would be a std::string , then testDataStream >> someString would read the comlplete line up to the next white space. But, with the existing structure it will work.

Anyway, you can use also a different functionality whi,ch will give you a little bit more security in case of malformed input lines. And that is, first read a complete line, then put it into a std::istringstream and extract the data from that.

If you would have complete different data structure, then you would probaly use the approach with std::getline with delimiter or the std::regex_token_iterator . But this would be to much for here.

Additionally, you obviously have a class hierachy. And you create derived classes based on a value read at runtime. This is usually solved with the Abstract Factory pattern.

I created a workable example, where you can see all these mechanisms. Please note: I will not use Plain C-Style char arrays for strings. And, I will never use raw pointers for owned memory. For that smart pointers shall be used.

#include <iostream>
#include <sstream>
#include <memory>
#include <map>
#include <functional>

std::istringstream testDataStream(R"(F,21,Hazel
M,49,Stephen)");

// Abstract base class "Person"
struct Person {

    // Constructor
    Person(const unsigned int a, const std::string& n) : age(a), name(n) {}

    // Do not forget the virtual destructor
    virtual ~Person() { std::cout << "Destructor Base\n\n\n"; }

    // The data: name and age
    unsigned int age{};
    std::string name{};

    // Since the inserter is not none member function, we will write
    // a separate virtual function to do the job polymorph
    virtual std::ostream& output(std::ostream& os) const = 0;

    // Inserter, will call our internal output function
    friend std::ostream& operator << (std::ostream& os, const Person& p) {
        return p.output(os);
    }
};

// Derived class for a male Person
struct Male : public Person {
    // Constructor
    Male(const unsigned int age, const std::string& name) : Person(age, name) {}
    virtual ~Male() { std::cout << "Destructor Male\n"; }

    // And output
    virtual std::ostream& output(std::ostream& os) const override {
        return os << "Male Person:\nAge:\t" << age << "\nName:\t" << name << '\n';
    }
};

// Derived class for a female Person
struct Female : public Person {

    // Constructor
    Female(const unsigned int age, const std::string& name) : Person(age, name) {}
    virtual ~Female() { std::cout << "Destructor Female\n"; }

    // And output
    virtual std::ostream& output(std::ostream& os) const override {
        return os << "Female Person:\nAge:\t" << age << "\nName:\t" << name << '\n';
    }
};

// "Creation" Functions for abstract factory
std::unique_ptr<Person> createMale(const unsigned int age, const std::string& name) { return std::make_unique<Male>(age, name); }
std::unique_ptr<Person> createFemale(const unsigned int age, const std::string& name) { return std::make_unique<Female>(age, name); }

// Abstract factory
struct AbstractFactory {
    // Abbreviation for finding
    using Map = std::map<char, std::function<std::unique_ptr<Person>(const unsigned int, const std::string&)>>;
    Map creationFunctions{
        {'F', createFemale },
        {'M', createMale }
    };
    std::unique_ptr<Person> create(const char selector, const unsigned int age, const std::string& name) {
        // If the selector is in the map
        if (Map::iterator searchResult{ creationFunctions.find(selector) }; searchResult != creationFunctions.end())
            // Then we call the corresponding creation function
            return creationFunctions[selector](age, name);
        else
            // No key found, the return nullptr (Empty Person());
            return std::unique_ptr<Person>();
    }
};
// Our abstract factor
AbstractFactory abstractFactory{};

// Driver code
int main() {

    // What we want to read
    char gender{};
    unsigned int age{};
    std::string name{};
    // This is a dummy
    char comma{};
    std::string line{};

//#define DIRECT_READ 
#ifdef DIRECT_READ

    // As long as there is data in the stream, read the gender and the comma
    while (testDataStream >> gender >> comma >> age >> comma >> name) {

#else

    while (std::getline(testDataStream, line)) {
        std::istringstream iss{ line };
        if (iss >> gender >> comma >> age >> comma >> name) {

#endif

            // Create a Person, either male or female
            std::unique_ptr<Person> person = abstractFactory.create(gender, age, name);

            // Polymorphism. Call the adequate member function
            std::cout << *person << '\n';

            // Do other stuff 
            // . . . 

        }
#ifndef DIRECT_READ
    }
#endif
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM