如何使用输入流重载将项目插入class中的映射成员?

How to use input stream overloading to insert item to map member in class?

我有 C++ class Question 来保存来自文件 questions.txt 的多项选择题和答案的数据:

更新: 我已经更新了 &operator>> 运算符重载我有一个:

  1. 只插入2道选择题中的第一道选择题"read the first Question "

文件中的数据questions.txt:

A programming language is used in this Course? 3

1. C
2. Pascal
3. C++
4. Assembly

What compiler can you use to compile the programs in this Course? 4

1. Dev-C++
2. Borland C++Builder
3. Microsoft Visual C++
4. All of the above

我正在尝试将多个答案插入到地图中。我只想问一下如何重载 operator>> 以迭代多个答案以将它们插入到地图中:

#include <string>
#include <iostream>
#include <sstream>
#include <map>
using namespace std;
class Question
{
    string question;
    int correctIndex;
    map<int,string> answers;


    friend std::istream &operator>>(std::istream &is, Question &q) {
        getline(is, q.question, '?');   // stops at '?'
        is>> q.correctIndex;
        string line;
        while (getline(is, line) && line.empty())  // skip leading blank lines
            ;
        while (getline(is,line) && !line.empty())  // read until blank line
        {
            int id;
            string ans;
            char pt;
            stringstream sst(line);    // parse the line;
            sst>>id>>pt;               // take number and the following point
            if (!sst  || id==0 || pt!='.')
                cout << "parsing error on: "<<line<<endl;
            else {
                getline (sst, ans);
                q.answers[id] = ans;
            }
        }
       return is;
    }
};

int main()
{
    ifstream readFile("questions.txt");//file stream
    vector<Question> questions((istream_iterator<Question>(readFile)), istream_iterator<Question>());
}

第一次改进

operator>>用于字符串时,它在第一个空白分隔符处停止。所以为了正确阅读你应该考虑的问题:

friend std::istream &operator>>(std::istream &is, Question &q) {
    getline(is, q.question, '?');   // stops at '?'
    return is>> q.correctIndex;
    ... // to do:  for the answers (see below)
}

您可以考虑采用类似的方法,从每个问题的 ID 开始阅读每个问题。不幸的是,在 int 上使用 operator>> 将不允许我们检测到最后的答案:阅读尝试将失败,因为下一个问题的非数字文本开始。

格式问题

您使用的格式有一些歧义:

  1. 空行是否是强制性的并标记答案的开始和结束?在这种情况下,最后一个问题是无效的:缺少答案的结尾)。
  2. 或者空白行是可选的并且必须被忽略吗?在这种情况下,第一个字符确定它是新问题(非数字)的开始还是新答案(数字)
  3. 或者是否总是期望一个问题恰好有 4 个答案?

选项 1:空行标记问题结束

想法是逐行读取并分别解析每一行:

    ...    
    string line; 
    while (getline(is, line) && line.empty())  // skip leading blank lines
       ;  
    do                                        // read until blank line 
    {
        int id;
        string ans; 
        char pt; 
        streamstring sst(line);    // parse the line;
        sst>>id>>pt;               // take number and the following point
        if (!sst  || id==0 || pt!='.') 
           cout << "parsing error on: "<<line<<endl; 
        else {
           getline (sst, ans); 
           q.answers[id] = ans; 
        }
        getline(is,line);
    } while (getline(is, line) && !line.empty());

注意:根据假设:答案结尾空行的缺失,会导致最后一题的阅读失败。理想情况下,您会发出一条错误消息来澄清(例如,文件意外结束)。用空的空白行更正输入文件将起作用(空行以新行结尾)。

备选方案 2:测试行的第一个字符以查看它是否仍然是下一个答案

另一种选择是查看要读取的第一个字符,以检查它是否是答案(以数字开头)、空行(要跳过),如果不是,则退出循环。

    ...    
    string line, ans;
    int c, id; 
    char pt; 
    while ((c = is.peek())!=EOF && (isdigit(c) || c=='\n')) {  // test first char without reading it
        getline(is, line); 
        if (!line.empty()) {
            stringstream sst(line);  
            ...    // parse the line as above
            }
        }
    }

使用此选项时,要求答案以换行符结尾(即尾随“\n”)。被 EOF 打断的未完成行将导致最后一个问题被忽略为失败。

您的代码有两个问题:跳过第一个答案并通读文件末尾。

在这对循环中:

while (getline(is, line) && line.empty())  // skip leading blank lines
    ;
while (getline(is,line) && !line.empty())  // read until blank line
{

第一个非空 line 将终止第一个循环,但随后您会立即再次调用 getline() 而无需实际读取其任何内容。这将跳过第一个答案选择。您需要确保第一次实际上没有调用 getline()。像...

// skip leading blank lines
while (getline(is, line) && line.empty()) {
    ;
}
for (; is && !line.empty(); getline(is, line)) {
    // ...
}

但是第二个也是更大的问题是,如果您通读文件末尾(就像您现在的代码所做的那样),最后的 operator>> 将导致 istreameof() ,这将忽略您流式传输的最后一个 Question。这很棘手,因为您有一个可变长度的输入流 - 我们不知道何时 运行 没有输入,直到我们实际上 运行 没有输入。

值得庆幸的是,我们可以让一切变得更简单。首先,我们不会读取输入的末尾来触发错误,而是使用第一次读取来让我们停止:

friend std::istream &operator>>(std::istream &is, Question &q) {
    if (!getline(is, q.question, '?')) {   // stops at '?'
        return is; 
    }

这样,如果我们提前到达 EOF,我们就会提前停止。其余的,我们可以使用skipws()来大大简化阅读。我们可以让 operator>> 跳过空行,而不是手动循环空行(这很难正确执行)。

当我们 运行 没有东西可读时,我们只是退出错误标志 - 因为我们不想要 fail() (如果我们尝试读取下一个索引,它实际上是下一个问题)或 eof()(我们完成了)被触发。

一共:

friend std::istream &operator>>(std::istream &is, Question &q) {
    if (!getline(is, q.question, '?')) {   // stops at '?'
        return is; 
    }

    is >> q.correctIndex;

    int id; 
    char pt; 
    string ans;

    is >> skipws;
    while (is >> id >> pt && getline(is, ans)) {
        q.answers[id] = ans;
    }

    // keep the bad bit, clear the rest
    is.clear(is.rdstate() & ios::badbit);
    return is; 
}   

现在还有点不完整。如果您没有读入 answers 任何与 correctIndex 匹配的内容,也许您想指出错误?在这种情况下,您也可以设置 ios::failbit