问题
I have a class in c++ called Airplane. 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. This line of input will then be split up using the commas and assigned to different private data members of the 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. I believe after the user types in "123,abc,hello," that line is stored somewhere and I can access that using istream somehow?
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. 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. tldr: i need to read user input from the console and separate the text based on commas, then assign to variables. my confusion is I dont know where to start or how to actually get the "123,abc,hello," line to process from the user. Thank you for reading.
UPDATED INFORMATION The code given below runs (chose example 3), but doesnt give the correct result. I call cin >> classname and input "1234,abcdaef,asdasd," and press enter. Then I call cout << classname and it prints the old data its storing and ignore the input given.
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.
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.
回答1:
Let us go step by step.
First, and most important, one complete input line will read using the function std::getline
. This function will read a complete line from whatever std::istream
and put it into a 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.
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
.
Since the reading of a line with std::getline
is simple, we will first concentrate on splitting up a string. 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
- Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
- Using old style
std::strtok
function. Maybe unsafe. Maybe should not be used any longer std::getline
. Most used implementation. But actually a "misuse" and not so flexible- Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.
Please see 4 examples in one piece of code.
#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, e.g. a std::vector
containing the substrings: So, "abc","12345" and "hello".
"abc" and "hello" can directly be stored (assigned to) in a string variable of your class. "12345" must be converted using an existing function, like for example std::stoi
, to an int
and assigned to a member variable.
The last step is to use all of this in a class (or struct).
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.
MyData md;
std::cin >> md;
I hope I could answer your question. If not, then please ask.
来源:https://stackoverflow.com/questions/63126616/how-do-i-read-and-parse-input-from-a-user-that-is-comma-separated-by-receiving-a