简体   繁体   English

如何通过在 c++ 中接收 std::istream object 来读取和解析逗号分隔的用户的输入?

[英]How do I read and parse input from a user that is comma separated by receiving an std::istream object in c++?

I have a class in c++ called Airplane.我在 c++ 中有一个名为 Airplane 的 class。 I need to create a read function using std::istream that lets a user type after a prompt in the console a line that is comma separated.我需要使用 std::istream 创建一个读取 function ,让用户在控制台中的提示后键入逗号分隔的行。 This line of input will then be split up using the commas and assigned to different private data members of the class.然后,这行输入将使用逗号分隔并分配给 class 的不同私有数据成员。 As an example, if the user types into the console "abc,12345,hello," then I would need to parse that line and assign abc to one variable, 12345 to another and hello to the last.例如,如果用户在控制台中键入“abc,12345,hello”,那么我需要解析该行并将 abc 分配给一个变量,将 12345 分配给另一个变量,并将 hello 分配给最后一个变量。 I believe after the user types in "123,abc,hello," that line is stored somewhere and I can access that using istream somehow?我相信在用户输入“123,abc,hello”之后,该行存储在某处,我可以以某种方式使用 istream 访问它吗?

What I have so far is below:到目前为止我所拥有的如下:

std::istream& Airplane::read(std::istream& in) {
   if (comma_separated == true) {
   // parse the line inputted by the user and then assign it to 3 variables
   // after getting the input somehow assign to variables
   this->first_var = info_before_first_comma;
   this->second_var = second_comma_text;
   etc...
   }
}

I believe I also need some sort of overload operator function to pass the class to, which then calls the read function above to process the class data. I believe I also need some sort of overload operator function to pass the class to, which then calls the read function above to process the class data. Something possibly like below?可能像下面这样的东西?

std::istream& operator>>(std::istream& output, Airplane& airplane) {}

That way I could create a class, then call cin >> class_name and it would take in input, process it, and assign it to that classes variables.这样我就可以创建一个 class,然后调用 cin >> class_name,它会接受输入,处理它,并将其分配给该类变量。 tldr: i need to read user input from the console and separate the text based on commas, then assign to variables. tldr:我需要从控制台读取用户输入并根据逗号分隔文本,然后分配给变量。 my confusion is I dont know where to start or how to actually get the "123,abc,hello," line to process from the user.我的困惑是我不知道从哪里开始或如何真正从用户那里获得“123,abc,hello”行来处理。 Thank you for reading.感谢您的阅读。

UPDATED INFORMATION The code given below runs (chose example 3), but doesnt give the correct result.更新信息 下面给出的代码运行(选择示例 3),但没有给出正确的结果。 I call cin >> classname and input "1234,abcdaef,asdasd," and press enter.我调用 cin >> classname 并输入“1234,abcdaef,asdasd”,然后按 Enter。 Then I call cout << classname and it prints the old data its storing and ignore the input given.然后我调用 cout << classname 并打印其存储的旧数据并忽略给定的输入。

When I try to do the following to check if the tokens are storing data:当我尝试执行以下操作以检查令牌是否正在存储数据时:

            std::cout << token[0] << std::endl;
            std::cout << token[1] << std::endl;
            std::cout << token[2] << std::endl;

I get a debug "vector subscript out of range" error.我得到一个调试“向量下标超出范围”错误。

This is how I stored the 3 values into my private data members, I have an int and 2 char arrays.这就是我将 3 个值存储到我的私有数据成员中的方式,我有一个 int 和 2 个 char arrays。

                this->store_int = std::stoi(token[0]);

                this->store_first_char = new char[token[1].length() + 1];
                strcpy(this->store_first_char, token[1].c_str());

                this->store_second_char = new char[token[2].length() + 1];
                strcpy(this->store_second_char, token[2].c_str());

But this didnt work either.但这也没有用。 One thing I forgot to clarify is that there is a comma at the end always if that matters.我忘记澄清的一件事是,如果这很重要,最后总是有一个逗号。 Thank you.谢谢你。

Let us go step by step.让我们一步一步来 go。

First, and most important, one complete input line will read using the function std::getline .首先,也是最重要的,一个完整的输入行将使用 function std::getline读取。 This function will read a complete line from whatever std::istream and put it into a std::string .这个 function 将从任何std::istream读取一个完整的行并将其放入std::string

Then, we need to split up the complete string into substrings.然后,我们需要将完整的字符串拆分为子字符串。 The substrings are separated by comma.子字符串用逗号分隔。 In the end, we would have an STL container, containing all the substrings.最后,我们将拥有一个包含所有子字符串的 STL 容器。

Then we make a sanity check and look at the number of substrings that we got after splitting up the string.然后我们进行完整性检查并查看拆分字符串后得到的子字符串的数量。 If the count is OK, then we do either store the strings directly, or convert them to the required data type, for example an int or a float .如果计数没问题,那么我们要么直接存储字符串,要么将它们转换为所需的数据类型,例如intfloat

Since the reading of a line with std::getline is simple, we will first concentrate on splitting up a string.由于使用std::getline读取一行很简单,我们将首先专注于拆分字符串。 This is also called tokenizing a string.这也称为对字符串进行标记。

I will show you several different approaches on how to tokenize a string:我将向您展示几种不同的方法来标记字符串:

Splitting a string into tokens is a very old task.将字符串拆分为标记是一项非常古老的任务。 There are many many solutions available.有许多可用的解决方案。 All have different properties.都有不同的属性。 Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.有些难以理解,有些难以开发,有些更复杂,更慢或更快或更灵活或不灵活。

Alternatives备择方案

  1. Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.手工制作,许多变体,使用指针或迭代器,可能难以开发且容易出错。
  2. Using old style std::strtok function.使用旧式std::strtok function。 Maybe unsafe.也许不安全。 Maybe should not be used any longer也许不应该再使用了
  3. std::getline . std::getline Most used implementation.最常用的实现。 But actually a "misuse" and not so flexible但实际上是一种“误用”,并没有那么灵活
  4. Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape.使用专门为此目的开发的专用现代 function,最灵活且最适合 STL 环境和算法环境。 But slower.但是比较慢。

Please see 4 examples in one piece of code.请在一段代码中查看 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;
}

So, after having an initial string, like "abc,12345,hello", we will now have a container of std::string s, eg a std::vector containing the substrings: So, "abc","12345" and "hello".所以,在有了一个初始字符串之后,比如 "abc,12345,hello",我们现在将有一个std::string的容器,例如一个包含子字符串的std::vector :所以,"abc","12345" 和“你好”。

"abc" and "hello" can directly be stored (assigned to) in a string variable of your class. "abc" 和 "hello" 可以直接存储(分配给)在 class 的字符串变量中。 "12345" must be converted using an existing function, like for example std::stoi , to an int and assigned to a member variable. “12345”必须使用现有的 function(例如std::stoi )转换为int并分配给成员变量。

The last step is to use all of this in a class (or struct).最后一步是在 class(或结构)中使用所有这些。

This would look for example like this:这看起来像这样:

struct MyData {
    // Our data
    std::string item1{};
    int value{};
    std::string item2{};
    
    // Overwrite extractor operator
    friend std::istream& operator >> (std::istream& is, MyData& md) {
        if (std::string line{};std::getline(is, line)) {

            // Here we will store the sub strings
            std::vector<std::string> token{};

            // Put in an istringstream for further extraction
            std::istringstream iss{ line };
            
            // Split
            for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
                ;

            // Sanity check
            if (token.size() == 3) {
            
                // Assigns value to our data members
                md.item1 = token[0];
                md.value = std::stoi(token[1]);
                md.item2 = token[2];
            }

        }
        return is;
    }
};

Sorry to say, but this is uncompiled, not tested code.很抱歉,这是未编译的,未经测试的代码。 It shoud give you an idea, on how it could be implemented.它应该给你一个关于如何实施的想法。

And now you can use std::iostream to get data into your struct.现在您可以使用std::iostream将数据放入您的结构中。

MyData md;
std::cin >> md;

I hope I could answer your question.我希望我能回答你的问题。 If not, then please ask.如果没有,请询问。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM