【问题标题】:Difficulty reading from a file从文件中读取困难
【发布时间】:2020-07-27 21:14:55
【问题描述】:

我有一个逗号分隔值的文件

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

逗号之前的第一个字符被正确读取,但是当我发送它来读取它时,它会在不需要时询问用户输入。它不读取文件的其余部分,即“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, ',');
}

【问题讨论】:

  • 应该是is.getline(tName, 16)(没有第三个参数)。
  • 我还建议将char tName[16]; 更改为std::string tName;,然后使用std::getline(is, tName); 而不是is.getline(tName, 16)

标签: c++ fstream ostream istream


【解决方案1】:

基本上,您是在询问有关读取 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::getlinestd::regex_token_iterator 的方法。但这对这里来说太过分了。

此外,您显然有一个类层次结构。并且您基于在运行时读取的值创建派生类。这通常通过抽象工厂模式来解决。

我创建了一个可行的示例,您可以在其中看到所有这些机制。请注意:我将对字符串使用纯 C 样式 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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-10
    • 2018-07-29
    • 2016-06-30
    • 2021-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多