如何在 C++ 中显示文本文件?

how to display text file in c++?

我想在我的 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 文件:

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

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

额外的困难是您在数据字段中有 spaces。但是格式化输入 >> 的提取器运算符将停止,如果它看到白色 space。所以,可能只读取一条记录中的一半字段。

解决方法:必须先读取header文件,再读取数据行。 接下来,您必须知道文件是否真的是制表符分隔的。有时制表符会转换为 spaces。在那种情况下,我们需要重新创建一个字段在 a 记录中的起始位置。

无论如何,你需要阅读完整的一行,然后将其分成几部分。

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

众多可能示例之一:

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

不过说实话,有很多假设。众所周知,制表符处理容易出错。

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

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

示例:

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

但这还不是全部。

我们应该将属于记录的所有函数包装到结构记录中。数据库也一样。特别是我们想要覆盖提取器和插入器运算符。那么后面的输入输出就很简单了

我们会把这个留到以后再说。 . .


如果您能提供更多更好的关于源文件结构的信息,那么我会更新我的答案。

以下是您应该考虑的几件事。

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

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

有些列包含由多个单词组成的数据。 file >> 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;
}