【问题标题】:How to read a stream with comma separated values in C++?如何在 C++ 中读取带有逗号分隔值的流?
【发布时间】:2020-03-17 16:35:49
【问题描述】:

我想强调以下一个基本问题:

假设您有一个 CSV 文件并使用输入标题填充单元格

代码应从 .csv 文件中读取此内容并将结果写入 .csv 文件。还可以对输出案例总数加上案例平均值进行编码。这是一个示例表单,我想看看如何有效地采用它来完成这个基本示例。

   void create() 
{ 
    // file pointer 
    fstream fout; 

    // opens an existing csv file or creates a new file. 
    fout.open("reportcard.csv", ios::out | ios::app); 

    cout << "Enter the details of 5 students:"
        << " roll name maths phy chem bio"; 
    << endl; 

    int i, roll, phy, chem, math, bio; 
    string name; 

    // Read the input 
    for (i = 0; i < 5; i++) { 

        cin >> roll 
            >> name 
            >> math 
            >> phy 
            >> chem 
            >> bio; 

        // Insert the data to file 
        fout << roll << ", "
            << name << ", "
            << math << ", "
            << phy << ", "
            << chem << ", "
            << bio 
            << "\n"; 
    } 
} 

另外,读取特定记录

void read_record() 
{ 

    // File pointer 
    fstream fin; 

    // Open an existing file 
    fin.open("reportcard.csv", ios::in); 

    // Get the roll number 
    // of which the data is required 
    int rollnum, roll2, count = 0; 
    cout << "Enter the roll number "
        << "of the student to display details: "; 
    cin >> rollnum; 

    // Read the Data from the file 
    // as String Vector 
    vector<string> row; 
    string line, word, temp; 

    while (fin >> temp) { 

        row.clear(); 

        // read an entire row and 
        // store it in a string variable 'line' 
        getline(fin, line); 

        // used for breaking words 
        stringstream s(line); 

        // read every column data of a row and 
        // store it in a string variable, 'word' 
        while (getline(s, word, ', ')) { 

            // add all the column data 
            // of a row to a vector 
            row.push_back(word); 
        } 

        // convert string to integer for comparision 
        roll2 = stoi(row[0]); 

        // Compare the roll number 
        if (roll2 == rollnum) { 

            // Print the found data 
            count = 1; 
            cout << "Details of Roll " << row[0] << " : \n"; 
            cout << "Name: " << row[1] << "\n"; 
            cout << "Maths: " << row[2] << "\n"; 
            cout << "Physics: " << row[3] << "\n"; 
            cout << "Chemistry: " << row[4] << "\n"; 
            cout << "Biology: " << row[5] << "\n"; 
            break; 
        } 
    } 
    if (count == 0) 
        cout << "Record not found\n"; 
} 


  [1]: https://i.stack.imgur.com/q6VfZ.png

【问题讨论】:

  • 您忘记了c++ 标签。这看起来不是很小(如minimal reproducible example),而且也很难理解你有什么问题。您在代码上方提到的示例数据似乎与代码没有任何关系。该代码处理移动平均线等。
  • 添加了标签 C++。此外,还添加了一项声明,即平均病例总数也在愿望清单中。我不能从头开始创建一个最小值,因为 SO 中有很多示例,问题是如何导航以修改它们,以便为新用户 C++ 进行最基本的练习——就像我在这个问题上提出的那样。您能为这项倡议做出贡献吗?
  • 恐怕你误解了这个网站应该如何运作。人们通常不会为您执行/调整。相反,请尝试自己做,如果遇到困难,请向minimal reproducible example 提出您需要帮助的一个问题。
  • 您有简单的逗号分隔数据,还是带有引号和引号之类的花哨的 CSV 格式?
  • 现在好多了,虽然不是minimal reproducible example。如果没有人超过我,我会做出回答。 :-)

标签: c++ csv stream


【解决方案1】:

我主要专注于为 operator&lt;&lt;operator&gt;&gt; 添加重载,因为您之前对它们表现出了一些兴趣,并在代码中描述了它们在 cmets 中所做的事情。

由于您要混合来自逗号分隔的流和其他流的输入和输出,因此我为CSV 流以及流向/来自用户的重载添加了一个适配器。

首先,创建一个class 以将所有属于同一数据的数据保存在一个数据记录中。我在这里做了一个简单的struct,这是一个class,默认情况下public可以访问其成员。

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

// your student record
struct student {
    std::string name; // It's usually good to have larger types first so name goes first
    int roll;
    int math;
    int phy;
    int chem;
    int bio;
};

// read a student from an istream (like std::cin) - whitespace separated
std::istream& operator>>(std::istream& is, student& s) {
    return is >> s.roll >> s.name >> s.math >> s.phy >> s.chem >> s.bio;
}

// write a student to an ostream (like std::cout)
std::ostream& operator<<(std::ostream& os, const student& s) {
    return os << "Details of Roll " << s.roll << ":\n"
              << "Name: " << s.name << '\n'
              << "Maths: " << s.math << '\n'
              << "Physics: " << s.phy << '\n'
              << "Chemistry: " << s.chem << '\n'
              << "Biology: " << s.bio << '\n';
}
//--------------------------------------------------------------------------------------
// An adapter for comma separated streaming
struct CSVStudent {
    CSVStudent(student& s) : stud(s) {}
    CSVStudent(const CSVStudent&) = delete;

    // The CSVStudent holds a reference to a "student"
    student& stud;
};

// read a record from an istream - comma separated
std::istream& operator>>(std::istream& is, CSVStudent& csvstud) {
    std::string line;

    student& s = csvstud.stud; // an alias to the student to have to type less

    if(std::getline(is, line)) { // read a complete line
        // put the line in an istringstream for extraction:
        std::istringstream ss(line);

        char delim; // a dummy for reading commas

        // Extract the comma separated values. "delim" is not checked so it could be
        // any char breaking up the int:s.
        //
        // The below does things in the following order:
        // 1.  "ss >> s.roll >> delim"
        //     This extracts roll and a comma and returns
        //     a reference to ss, which is used in 2.
        // 2.  std::getline(ss, s.name, ',')
        //     Extracts a string until a comma is encountered.
        // 3.  Normal extraction for the rest of the int:s with the
        //     dummy variable "delim" where the commas are supposed to be.

        if(not(std::getline(ss >> s.roll >> delim, s.name, ',') >> s.math >> delim >>
               s.phy >> delim >> s.chem >> delim >> s.bio)) {
            // If we get here, the extraction from the istringstream failed, so set
            // the failstate on the istream too. Note the "not" on the line above.
            is.setstate(std::ios::failbit);
        }
    }
    return is;
}

// write a record to an ostream - comma separated
std::ostream& operator<<(std::ostream& os, const CSVStudent& csvstud) {
    const student& s = csvstud.stud;
    os << s.roll << ',' << s.name << ',' << s.math << ',' << s.phy << ',' << s.chem
       << ',' << s.bio << '\n';
    return os;
}
//--------------------------------------------------------------------------------------
// get all students in the file as a std::vector<student>
std::vector<student> read_student_file(const std::string& filename) {
    std::vector<student> retval;
    std::ifstream fin(filename);
    if(fin) { // file opened successfully
        student stud;
        CSVStudent csvstud{stud}; // holds a reference to stud

        // loop for as long as student records can be read successfully
        while(fin >> csvstud)       // use the csv sdapter
            retval.push_back(stud); // and put the stud in the vector
    }
    return retval;
}
//--------------------------------------------------------------------------------------
void create(const std::string& filename) {
    // open an existing csv file or creates a new file.
    std::ofstream fout(filename, std::ios::out | std::ios::app);
    if(fout) {
        std::cout << "Enter the details of 5 students:"
                     " roll name maths phy chem bio\n";

        // Read the input
        for(int i = 0; i < 5; i++) {
            student stud;
            std::cout << (i + 1) << ": ";
            if(std::cin >> stud) {
                // Insert the data to file if one was entered successfully
                fout << CSVStudent(stud); // uses the adapters operator<<
            } else {
                std::cerr << "You failed to enter data for student " << (i + 1) << '\n';
                break;
            }
        }
    }
}
//--------------------------------------------------------------------------------------
int main() {
    std::string filename = "reportcard.csv";

    std::vector<student> students = read_student_file(filename);

    std::cout << "There are " << students.size() << " students in the file.\n";
    if(not students.empty()) {
        // show the last record if there are any records in the file
        std::cout << "Record " << students.size() << " is:\n\n";
        std::cout << students.back() << '\n';
    }

    // create 5 new records
    create(filename);
}

如果reportcard.csv 包含这个:

1,Ted,1,2,3,4
2,Foo,2,3,4,5
3,Bar,3,4,5,6
4,Baz,4,5,6,7
5,Bork,5,6,7,8

程序应该像这样启动:

There are 5 students in the file.
Record 5 is:

Details of Roll 5:
Name: Bork
Maths: 5
Physics: 6
Chemistry: 7
Biology: 8

Enter the details of 5 students: roll name maths phy chem bio
1: <and here is where you're supposed to enter the first of 5 new students>

【讨论】:

  • 一个更好的方法是有一个简单的包装类(CVSStudent),它只保存对学生的引用,但是当序列化器/反序列化像 CVS 文件一样读取/写入时,而普通学生流入人类可读的形式:`std::cin >> CVSStudent{studentObject}; std::cout
  • @MartinYork 是的,我同意添加一层适配器会使其更加通用并保持清洁。我可能会回来补充。
  • @Mohammad 关注点分离很好。通过使student 简单并且仅能够与任何ostream/istream 进行基本流式传输,可以轻松添加以其他特定方式执行流式传输的支持类、适配器,例如csv 等。
  • @Mohammad 您需要在适用的情况下添加验证器,并根据您认为合适的值拒绝或接受值。要删除一个值,您可以简单地将其从std::vector&lt;student&gt; 中删除,然后将向量中的所有学生写入文件以覆盖您之前的内容。
  • 它可以通过多种方式完成。如果 roll 应该是一个唯一的数字,您可以使用 roll 作为 keystudent 作为 value 来创建 std::map&lt;int, student&gt; studentmap;。要让某个学生使用roll,然后可以使用student&amp; stud = studentmap.at(roll); 完成,或者您可以将vectorstd::find 学生保留在其中。无限可能。 :-)
猜你喜欢
  • 1970-01-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多