Трудность чтения из файла - PullRequest
0 голосов
/ 15 апреля 2020

У меня есть файл с разделенными запятыми значениями

M,21,Hazel
F,49,Stephen

Я отправляю ifstream в функцию, которая принимает istream для чтения строки.

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;
}

Первый символ до запятая читается правильно, но когда я отправляю ее на чтение, она запрашивает ввод пользователя, когда она не нужна. Он не читает остальную часть файла ie "49, Стивен"

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, ',');
}

1 Ответ

0 голосов
/ 16 апреля 2020

По сути, вы спрашиваете о чтении CSV-файла и разбиении его на токены. Если вы ищете это здесь на SO, вы найдете много постов, которые объясняют, как это сделать.

Но в вашем случае это может быть проще. Если гарантируется, что исходный файл имеет точно такой же формат, без дополнительных пробелов до или после запятой, то вы можете использовать стандартные механизмы извлечения для потоков. Вы просто прочитаете запятую в фиктивную переменную. Это может выглядеть так:

// 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) {

Это будет читать первый символ в переменной gender, а затем читать запятую и помещать ее в переменную comma. Это мы просто не будем использовать. Затем мы продолжим извлекать больше значений таким же образом.

Если структура исходного файла отличается, он не будет работать. Например, если первая переменная будет std::string, то testDataStream >> someString будет читать полную строку до следующего пробела. Но с существующей структурой это будет работать.

В любом случае, вы можете использовать и другую функциональность, которая обеспечит вам немного больше безопасности в случае неправильно сформированных входных линий. Иными словами, сначала прочитайте полную строку, затем поместите ее в std::istringstream и извлеките из нее данные.

Если бы у вас была полная структура данных, то вы, вероятно, использовали бы подход с std::getline с разделителем или std::regex_token_iterator. Но это было бы очень много для здесь.

Кроме того, у вас, очевидно, есть иерархия классов. И вы создаете производные классы на основе значения, прочитанного во время выполнения. Обычно это решается с помощью шаблона Abstract Factory.

Я создал работоспособный пример, где вы можете увидеть все эти механизмы. Пожалуйста, обратите внимание: я буду не использовать простые C -Style char массивы для строк. И я никогда не буду использовать необработанные указатели для собственной памяти. Для этого должны использоваться умные указатели.

#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;
}
...