簡體   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++?

我在 c++ 中有一個名為 Airplane 的 class。 我需要使用 std::istream 創建一個讀取 function ,讓用戶在控制台中的提示后鍵入逗號分隔的行。 然后,這行輸入將使用逗號分隔並分配給 class 的不同私有數據成員。 例如,如果用戶在控制台中鍵入“abc,12345,hello”,那么我需要解析該行並將 abc 分配給一個變量,將 12345 分配給另一個變量,並將 hello 分配給最后一個變量。 我相信在用戶輸入“123,abc,hello”之后,該行存儲在某處,我可以以某種方式使用 istream 訪問它嗎?

到目前為止我所擁有的如下:

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. 可能像下面這樣的東西?

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

這樣我就可以創建一個 class,然后調用 cin >> class_name,它會接受輸入,處理它,並將其分配給該類變量。 tldr:我需要從控制台讀取用戶輸入並根據逗號分隔文本,然后分配給變量。 我的困惑是我不知道從哪里開始或如何真正從用戶那里獲得“123,abc,hello”行來處理。 感謝您的閱讀。

更新信息 下面給出的代碼運行(選擇示例 3),但沒有給出正確的結果。 我調用 cin >> classname 並輸入“1234,abcdaef,asdasd”,然后按 Enter。 然后我調用 cout << classname 並打印其存儲的舊數據並忽略給定的輸入。

當我嘗試執行以下操作以檢查令牌是否正在存儲數據時:

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

我得到一個調試“向量下標超出范圍”錯誤。

這就是我將 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());

但這也沒有用。 我忘記澄清的一件事是,如果這很重要,最后總是有一個逗號。 謝謝你。

讓我們一步一步來 go。

首先,也是最重要的,一個完整的輸入行將使用 function std::getline讀取。 這個 function 將從任何std::istream讀取一個完整的行並將其放入std::string

然后,我們需要將完整的字符串拆分為子字符串。 子字符串用逗號分隔。 最后,我們將擁有一個包含所有子字符串的 STL 容器。

然后我們進行完整性檢查並查看拆分字符串后得到的子字符串的數量。 如果計數沒問題,那么我們要么直接存儲字符串,要么將它們轉換為所需的數據類型,例如intfloat

由於使用std::getline讀取一行很簡單,我們將首先專注於拆分字符串。 這也稱為對字符串進行標記。

我將向您展示幾種不同的方法來標記字符串:

將字符串拆分為標記是一項非常古老的任務。 有許多可用的解決方案。 都有不同的屬性。 有些難以理解,有些難以開發,有些更復雜,更慢或更快或更靈活或不靈活。

備擇方案

  1. 手工制作,許多變體,使用指針或迭代器,可能難以開發且容易出錯。
  2. 使用舊式std::strtok function。 也許不安全。 也許不應該再使用了
  3. std::getline 最常用的實現。 但實際上是一種“誤用”,並沒有那么靈活
  4. 使用專門為此目的開發的專用現代 function,最靈活且最適合 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;
}

所以,在有了一個初始字符串之后,比如 "abc,12345,hello",我們現在將有一個std::string的容器,例如一個包含子字符串的std::vector :所以,"abc","12345" 和“你好”。

"abc" 和 "hello" 可以直接存儲(分配給)在 class 的字符串變量中。 “12345”必須使用現有的 function(例如std::stoi )轉換為int並分配給成員變量。

最后一步是在 class(或結構)中使用所有這些。

這看起來像這樣:

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

很抱歉,這是未編譯的,未經測試的代碼。 它應該給你一個關於如何實施的想法。

現在您可以使用std::iostream將數據放入您的結構中。

MyData md;
std::cin >> md;

我希望我能回答你的問題。 如果沒有,請詢問。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM