【问题标题】:how to display text file in c++?如何在 C++ 中显示文本文件?
【发布时间】:2022-01-10 09:40:19
【问题描述】:

我想在我的 c++ 程序中显示文本文件,但什么也没有出现,程序刚刚结束。我在这里使用结构。我以前使用过这种方法,但现在我不确定为什么它不起作用。我希望有人能帮助我。非常感谢。

struct Records{
    int ID;
    string desc;
    string supplier;
    double price;
    int quantity;
    int rop;
    string category;
    string uom; 
    
}record[50];
void inventory() {
    int ID, quantity, rop;
    string desc, supplier, category, uom;
    double price;

    ifstream file("sample inventory.txt");
    
    if (file.fail()) {
        cout << "Error opening records file." <<endl;
        exit(1);
    }
    
    int i = 0;
    while(! file.eof()){
        file >> ID >> desc >> supplier >> price >> quantity >> rop >> category >> uom;
        record[i].ID = ID;
        record[i].desc = desc;
        record[i].supplier = supplier;
        record[i].price = price;
        record[i].quantity = quantity;
        record[i].rop = rop;
        record[i].category = category;
        record[i].uom = uom;
        i++;
    }  
    
    for (int a = 0; a < 15; a++) {
        cout << "\n\t";
        cout.width(10); cout << left << record[a].ID;
        cout.width(10); cout << left << record[a].desc;
        cout.width(10); cout << left << record[a].supplier;
        cout.width(10); cout << left << record[a].price;
        cout.width(10); cout << left << record[a].quantity;
        cout.width(10); cout << left << record[a].rop;
        cout.width(10); cout << left << record[a].category;
        cout.width(10); cout << left << record[a].uom << endl;
    }
    
    file.close();
}

这是txt文件:

【问题讨论】:

  • @RetiredNinja record 是一个结构变量
  • minimal reproducible example中显示它是如何在你的程序中声明的。
  • 文件的第一行以字母开头。您从文件中读取的第一个地方是尝试读取一个数字。您没有任何错误检查,因此您不知道它失败了。您还会遇到包含空格的字段的问题。 &gt;&gt; 停在空白处,因此所有这些列都将关闭,并且可能由于数据与不兼容的类型对齐而失败。
  • @RetiredNinja 哦,我现在不知何故明白了。谢谢!但是,有什么建议我该如何显示它?很快,我将只需要使用该文本文件的一列。

标签: c++ struct fstream display txt


【解决方案1】:

您应该考虑以下几点。

  1. 根据需要声明变量。不要在函数顶部声明它们。它使代码更具可读性。
  2. 使用文件的完整路径以避免混淆。例如"c:/temp/sample inventory.txt"
  3. if ( ! file ) 更短。
  4. 要在循环中读取数据,请将实际读取用作条件while( file &gt;&gt; ID &gt;&gt;... )。这将揭示您的问题的原因。
  5. 了解setw 操纵器。
  6. file 的析构函数将关闭流 - 你不需要调用 close()

您的文件格式由标题和数据组成。您没有阅读标题。您正在尝试直接读取数据。您尝试将标头与各种数据类型进行匹配:字符串、整数、浮点数;但标题完全由文字组成。您的尝试将使流无效,并且所有后续读取尝试都将失败。所以,首先丢弃标题——你可以使用getline

某些列包含由多个单词组成的数据。 file &gt;&gt; supplier一个词,而不是两个或更多。所以你会得到"Mongol",而不是"Mongol Inc." 你的数据格式列之间需要一个分隔符。否则,您将无法判断该列的结束位置。如果您再次添加分隔符,您可以使用getline 来读取字段。

CATEGORY 列是空的。尝试读取它会导致从不同的列读取。添加分隔符也可以解决空类别列的问题。

如果您使用逗号作为分隔符,这就是您的第一行的样子:

ID,PROD DESC,SUPPLIER,PRICE,QTY,ROP,CATEGORY,UOM
001,Pencil,Mongol Inc.,8,200,5,,pcs

不同格式的解决方案是将字符串定义为零个或多个用引号括起来的字符

001 "Pencil" "Mongol Inc." 8 200 5 "" "pcs"

并利用quoted 操纵器(注意空类别字符串):

const int max_records_count = 50;
Record records[max_records_count];

istream& read_record(istream& is, Record& r) // returns the read record in r
{
  return is >> r.ID >> quoted(r.desc) >> quoted(r.supplier) >> r.price >> r.quantity >> r.rop >> quoted(r.category) >> quoted(r.uom);
}

istream& read_inventory(istream& is, int& i) // returns the number of read records in i
{
  //...
  for (i = 0; i < max_records_count && read_record(is, records[i]); ++i)
    ; // no operation
  return is;
}

【讨论】:

    【解决方案2】:

    很遗憾,您的文本文件不是典型的 CSV 文件,由逗号等字符分隔。行中的条目似乎由制表符分隔。但这是我的猜测。反正。源文件的结构使其更难阅读。

    此外,该文件有一个标题,并且在读取第一行并尝试将单词“ID”读入 int 变量时,此转换将失败。流的故障位已设置,从那时起,对该流的任何 iostream 函数的所有进一步访问将不再执行任何操作。它会忽略你所有进一步的阅读请求。

    额外的困难是数据字段中有空格。但格式化输入&gt;&gt; 的提取器运算符将停止,如果它看到一个空格。所以,可能只读取记录中的一半字段。

    解决方案:您必须先读取头文件,然后读取数据行。 接下来,您必须知道文件是否真的是制表符分隔的。有时制表符会转换为空格。在这种情况下,我们需要重新创建记录中字段的起始位置。

    在任何情况下,您都需要阅读完整的一行,然后将其分成几部分。

    对于第一种解决方案,我假设制表符分隔字段。

    许多可能的例子之一:

    #include <iostream>
    #include <string>
    #include <fstream>
    #include <sstream>
    #include <vector>
    #include <iomanip>
    
    const std::string fileName{"r:\\sample inventory.txt"};
    
    struct Record {
        int ID;
        std::string desc;
        std::string supplier;
        double price;
        int quantity;
        int rop;
        std::string category;
        std::string uom;
    
    };
    using Database = std::vector<Record>;
    
    int main() {
    
        // Open the source text file with inventory data and check, if it could be opened
        if (std::ifstream ifs{ fileName }; ifs) {
    
            // Here we will store all data
            Database database{};
    
            // Read the first header line and throw it away
            std::string line{};
            std::string header{};
    
            if (std::getline(ifs, header)) {
    
                // Now read all lines containing record data
                while (std::getline(ifs, line)) {
    
                    // Now, we read a line and can split it into substrings. Assuming the tab as delimiter
                    // To be able to extract data from the textfile, we will put the line into a std::istrringstream
                    std::istringstream iss{ line };
    
                    // One Record
                    Record record{};
                    std::string field{};
    
                    // Read fields and put in record
                    if (std::getline(iss, field, '\t')) record.ID = std::stoi(field);
                    if (std::getline(iss, field, '\t')) record.desc = field;
                    if (std::getline(iss, field, '\t')) record.supplier = field;
                    if (std::getline(iss, field, '\t')) record.price = std::stod(field);
                    if (std::getline(iss, field, '\t')) record.quantity = std::stoi(field);
                    if (std::getline(iss, field, '\t')) record.rop = std::stoi(field);
                    if (std::getline(iss, field, '\t')) record.category = field;
                    if (std::getline(iss, field))       record.uom = field;
    
                    database.push_back(record);
                }
    
                // Now we read the complete database
                // Show some debug output.
                std::cout << "\n\nDatabase:\n\n\n";
                // Show all records
                for (const Record& r : database)
                    std::cout << std::left << std::setw(7) << r.ID << std::setw(20) << r.desc 
                    <<  std::setw(20) << r.supplier << std::setw(8) << r.price << std::setw(7) 
                    << r.quantity << std::setw(8) << r.rop << std::setw(20) << r.category << std::setw(8) << r.uom << '\n';
            }
        }
        else std::cerr << "\nError: COuld not open source file '" << fileName << "'\n\n";
    }
    

    但老实说,有很多假设。而且标签处理是出了名的容易出错。

    所以,让我们采用下一种方法,根据它们在标题字符串中的位置提取数据。因此,我们将检查每个标题字符串的开始位置,并使用此信息稍后将完整的行拆分为子字符串。

    我们将使用字段描述符列表并在标题行中搜索它们的起始位置和宽度。

    例子:

    #include <iostream>
    #include <string>
    #include <fstream>
    #include <sstream>
    #include <vector>
    #include <iomanip>
    #include <array>
    
    const std::string fileName{"r:\\sample inventory.txt"};
    
    struct Record {
        int ID;
        std::string desc;
        std::string supplier;
        double price;
        int quantity;
        int rop;
        std::string category;
        std::string uom;
    };
    constexpr size_t NumberOfFieldsInRecord = 8u;
    using Database = std::vector<Record>;
    
    int main() {
    
        // Open the source text file with inventory data and check, if it could be opened
        if (std::ifstream ifs{ fileName }; ifs) {
    
            // Here we will store all data
            Database database{};
    
            // Read the first header line and throw it away
            std::string line{};
            std::string header{};
    
            if (std::getline(ifs, header)) {
    
                // Analyse the header
                // We have 8 elements in one record. We will store the positions of header items
                std::array<size_t, NumberOfFieldsInRecord> startPosition{};
                std::array<size_t, NumberOfFieldsInRecord> fieldWidth{};
                const std::array<std::string, NumberOfFieldsInRecord> expectedHeaderNames{ "ID","PROD DESC","SUPPLIER","PRICE","QTY","ROP","CATEGORY","UOM"};
      
                for (size_t k{}; k < NumberOfFieldsInRecord; ++k)
                    startPosition[k] = header.find(expectedHeaderNames[k]);
                for (size_t k{ 1 }; k < NumberOfFieldsInRecord; ++k)
                    fieldWidth[k - 1] = startPosition[k] - startPosition[k - 1];
                fieldWidth[NumberOfFieldsInRecord - 1] = header.length() - startPosition[NumberOfFieldsInRecord - 1];
    
                // Now read all lines containing record data
                while (std::getline(ifs, line)) {
    
                    // Now, we read a line and can split it into substrings. Based on poisition and field width
                    // To be able to extract data from the textfile, we will put the line into a std::istrringstream
                    std::istringstream iss{ line };
    
                    // One Record
                    Record record{};
                    std::string field{};
    
                    // Read fields and put in record
                    field = line.substr(startPosition[0], fieldWidth[0]);  record.ID = std::stoi(field);
                    field = line.substr(startPosition[1], fieldWidth[1]);  record.desc = field;
                    field = line.substr(startPosition[2], fieldWidth[2]);  record.supplier = field;
                    field = line.substr(startPosition[3], fieldWidth[3]);  record.price = std::stod(field);
                    field = line.substr(startPosition[4], fieldWidth[4]);  record.quantity = std::stoi(field);
                    field = line.substr(startPosition[5], fieldWidth[5]);  record.rop = std::stoi(field);
                    field = line.substr(startPosition[6], fieldWidth[6]);  record.category = field;
                    field = line.substr(startPosition[7], fieldWidth[7]);  record.uom = field;
    
                    database.push_back(record);
                }
    
                // Now we read the complete database
                // Show some debug output.
                std::cout << "\n\nDatabase:\n\n\n";
    
                // Header 
                for (size_t k{}; k < NumberOfFieldsInRecord; ++k)
                    std::cout << std::left << std::setw(fieldWidth[k]) << expectedHeaderNames[k];
                std::cout << '\n';
    
                // Show all records
                for (const Record& r : database)
                    std::cout << std::left << std::setw(fieldWidth[0]) << r.ID << std::setw(fieldWidth[1]) << r.desc
                    <<  std::setw(fieldWidth[2]) << r.supplier << std::setw(fieldWidth[3]) << r.price << std::setw(fieldWidth[4])
                    << r.quantity << std::setw(fieldWidth[5]) << r.rop << std::setw(fieldWidth[6]) << r.category << std::setw(fieldWidth[7]) << r.uom << '\n';
            }
        }
        else std::cerr << "\nError: COuld not open source file '" << fileName << "'\n\n";
    }
    

    但这还不是全部。

    我们应该将属于一个记录的所有函数包装到结构记录中。数据库也是如此。尤其是我们要覆盖提取器和插入器操作符。那么后面的输入输出就很简单了。

    我们会保存这个以备后用。 . .


    如果您可以提供有关源文件结构的更多更好的信息,那么我将更新我的答案。

    【讨论】:

    • 非常感谢您的回答,我现在在您的回答的帮助下处理我的代码。我不知何故知道现在该做什么。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2012-07-02
    • 2019-03-13
    • 1970-01-01
    • 2021-07-04
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    • 1970-01-01
    相关资源
    最近更新 更多