如何创建一个函数来从 C++ 中的文本文件中读取并忽略某些字符并将数据值存储在适当的变量中?

How to create a function to read from a text file in C++ and ignore certain character and store data values in appropriate variables?

我的 class date 定义如下。我已将函数 write(ostream &o) 编程为以这种格式 27/May/2020 编写日期,以便用户易于阅读。现在我想从一个包含上述格式日期的文件中读取。那么我如何在 C++ 中编写一个函数来忽略 '/' 字符并将值 27 存储在 int day 中,值 "May" 存储在 string month 中以及 int year 中的值 2010。另外,我如何设计一个函数来忽略某个字符并能够在 C++ 中以各种数据类型存储值?

class date{
    private :
        int day;
        string month;
        int year;

        public:
            date()
            {
                day=0;
                month="NULL";
                year=2020;
            }

            void getdate()
            {
                cout<<"Enter Date : ";
                cin>>day>>month;
            }

            void write(ostream &o) const  // to write text file or screen
            {
                o<<day<<'/'<<month.c_str()<<'/'<<year;
            }

};

您可以使用一种非常简单的方法,使用 属性 流 extraction/insertion 运算符 return 对流的引用。有了它,您可以像您的示例 cin>>day>>month;.

中那样链接提取语句

这样你就可以做一个衬里,就像下面的例子一样:

#include <iostream>
#include <string>
#include <sstream>

std::istringstream iss{"27/May/2020"};

int main() {

    int day{};
    std::string month{};
    int year{};
    char slash{};

    if ((std::getline(iss >> day >> slash, month, '/') >> year) && (slash == '/')) {

        std::cout << day << ' ' << month << ' ' << year << '\n';
    }
    else {
        std::cerr << "\nError: Wrong input format\n";
    }
    return 0;
}

编辑

OP 要求对 one liner 进行更多解释。

if ((std::getline(iss >> day >> slash, month, '/') >> year) && (slash == '/')) {

好的。如您所知,嵌套函数将从内到外求值。例如,如果您有:pow(sqrt(x+100),y+2),那么首先计算 x+100,然后计算 sqrt,然后计算 y+2,最后计算 pow。很好,明白了。

看看我们的一行,这意味着,首先 iss >> day 将被评估。在开放流 iss 中(您也可以使用 std::cinstd::ifstream 或基本上任何 istream)我们在开头有:“27/May/2020”。上面的提取操作会把数字2和7提取出来,转换成整数27,就是我们的一天

现在,输入流包含“/May/2020”,因为已经提取了 2 和 7。

操作iss >> day现已完成。重载的提取器运算符 >> 将始终 return 对调用它的 istream 的引用。意思是,iss >> day 将 return "iss".

这种运算符的原型通常定义为

std::istream& operator >> (std::istream& is, SomeClass& sc) { 
    // Do something with SomeClass sc . . .
    return is; }

您可以看到,流是 returned。这使您可以链接提取器运算符。在对第一个 iss >> day 语句求值后,该行的其余部分将如下所示:

 if ((std::getline(iss >> slash, month, '/') >> year) && (slash == '/')) 

因为 iss >> day 已经完成并且 return 已发布。请记住,我们的流现在包含“/May/2020”。并且,接下来我们将做:iss >> slash。也就是说,我们将从流中提取斜杠并将其存储在变量斜杠中。提取器操作员将再次 return iss。流中的数据现在是 "May/2020"。新的班轮将是:

if ((std::getline(iss, month, '/') >> year) && (slash == '/'))

这个我们可以理解。 std::getline will extract a string from the stream, until it sees a slash. That string is stored in the variable month. The data in the stream will now be "2020". And guest what?std::getline```` 还将 return 对给定流的引用。这样一来,一个班轮现在将是

 if ((iss >> year) && (slash == '/'))

我们知道,现在会发生什么:iss >> year 将被评估。 “2020”将转换为整数 2020。流现在为空。一切都被提取出来了。运营商 >> 将再次 return 发出。给我们:

if ((iss) && (slash == '/'))

嗯,if 需要一个布尔表达式,这如何与 iss 一起使用?可以解释为:如果期望一个布尔表达式,并且非常聪明,一个流作为重载的布尔运算符。请阅读here。如果流处于良好状态或失败,则此运算符 returns。

如果整个链中的某些提取操作失败,则 bool 运算符将 return 为假。然后我们会显示错误信息。

作为附加检查,我们验证变量 "slash" 的内容。

所以,就是这样。我希望,我能以一种可以理解的方式解释它。


结束编辑


但也许更安全,如果你先阅读完整的一行 (std::stringstd::getline 然后拆分这一行。

我也会向您展示一些示例代码。

查看一些常见的字符串拆分模式:

将字符串拆分为标记是一项非常古老的任务。有许多可用的解决方案。都有不同的属性。有的难于理解,有的难于开发,有的比较复杂,较慢或较快或较灵活或不灵活。

备选方案

  1. 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
  2. 使用旧式 std::strtok 函数。也许不安全。也许不应该再使用了
  3. std::getline。最常用的实现。但实际上一个"misuse"并没有那么灵活
  4. 使用专门为此目的开发的专用现代功能,最灵活,最适合 STL 环境和算法环境。但是比较慢。

一段代码请看4个例子

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>

using Container = std::vector<std::string>;
std::regex delimiter{ "," };


int main() {

    // Some function to print the contents of an STL container
    auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
        std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };

    // Example 1:   Handcrafted -------------------------------------------------------------------------
    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
        Container c{};

        // Search for comma, then take the part and add to the result
        for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {

            // So, if there is a comma or the end of the string
            if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {

                // Copy substring
                c.push_back(stringToSplit.substr(startpos, i - startpos));
                startpos = i + 1;
            }
        }
        print(c);
    }

    // Example 2:   Using very old strtok function ----------------------------------------------------------
    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
        Container c{};

        // Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
        for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
            c.push_back(token);
        }

        print(c);
    }

    // Example 3:   Very often used std::getline with additional istringstream ------------------------------------------------
    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
        Container c{};

        // Put string in an std::istringstream
        std::istringstream iss{ stringToSplit };

        // Extract string parts in simple for loop
        for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
            ;

        print(c);
    }

    // Example 4:   Most flexible iterator solution  ------------------------------------------------

    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };


        Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
        //
        // Everything done already with range constructor. No additional code needed.
        //

        print(c);


        // Works also with other containers in the same way
        std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});

        print(c2);

        // And works with algorithms
        std::deque<std::string> c3{};
        std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));

        print(c3);
    }
    return 0;
}