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

问题描述

我的class date定义如下。我已经对函数write(ostream &o)进行了编程,以这种格式写入日期,27/May/2020以便用户轻松阅读。现在我想从包含上述格式日期的文件中读取。那么我如何在 C++ 中编写一个忽略'/'字符并存储值的函数27,例如 in int day"May"instring month和 in 的2010int year。另外,我如何设计一个函数来忽略某个字符并能够将值存储在 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;
            }

};

标签: c++file-handling

解决方案


您可以使用一种非常简单的方法,使用流提取/插入运算符的属性返回对流的引用。有了它,您可以像在您的示例中那样链接提取语句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 要求对一个班轮进行更多解释。

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::cinor anstd::ifstream或基本上 any )中:“27/May/2020”。istream上面的提取操作将提取数字 2 和 7 并将其转换为整数 27。那是我们的一天。

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

手术iss >> day现已结束。重载的提取器运算符>>将始终返回对istream调用它的引用。意思是,iss >> day将返回“iss”。

这种算子的原型通常定义为

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

您可以看到,流已返回。这使您可以链接提取器运算符。在评估完第一个iss >> day语句之后,该行的其余部分将如下所示:

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

因为iss >> day已经完成并返回了iss。请记住,我们的流现在包含“/May/2020”。并且,接下来我们将做:iss >> slash. 意思是,我们将从流中提取斜杠并将其存储在变量斜杠中。提取器运算符将再次返回 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```` 也将返回对给定流的引用。这样一来,一个班轮现在将是

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

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

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

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

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

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

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


结束编辑


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

我还将向您展示一些示例代码。

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

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

备择方案

  1. 手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
  2. 使用旧式std::strtok功能。也许不安全。也许不应该再使用了
  3. std::getline. 最常用的实现。但实际上是一种“误用”,并没有那么灵活
  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;
}

推荐阅读